From 883009770ffc6b5fc51d47f09440ce399436d5bf Mon Sep 17 00:00:00 2001 From: Zeeshaun Date: Wed, 25 Mar 2026 11:54:22 -0500 Subject: [PATCH] Adding --- .../.editorconfig | 37 + LCU.WebApps.External.OnlineLoanApp/.gitignore | 412 + .../CHANGELOG.md | 838 + LCU.WebApps.External.OnlineLoanApp/Dockerfile | 33 + LCU.WebApps.External.OnlineLoanApp/README.md | 372 + .../install_dependencies.ps1 | 1 + .../_kube_local_testing/kind.cluster.config | 7 + .../_kube_local_testing/setup_and_deploy.ps1 | 17 + .../_kube_local_testing/teardown.ps1 | 1 + .../angular.json | 174 + .../azdo_pipelines/azure-k8s-pipeline.yaml | 53 + .../azure-pipelines-feature-branches.yaml | 10 + .../azdo_pipelines/azure-pipelines-prd.yaml | 10 + .../azdo_pipelines/azure-pipelines-qa.yaml | 10 + .../azdo_pipelines/azure-pipelines-uat.yaml | 10 + .../config-files/prd-pvars.yaml | 7 + .../azdo_pipelines/config-files/qa-pvars.yaml | 7 + .../config-files/uat-pvars.yaml | 7 + .../docker/nginx.conf | 24 + .../e2e/protractor.conf.js | 35 + .../credit-card-full-spouses.e2e-spec.ts | 153 + .../credit-card-go-back.e2e-spec.ts | 129 + .../credit-card-maximum.e2e-spec.ts | 144 + .../credit-card-minimal.e2e-spec.ts | 85 + .../credit-card-tattle-spouses.e2e-spec.ts | 130 + .../credit-card/pages/page-disclosures.po.ts | 34 + .../e2e/src/credit-card/pages/page-one.po.ts | 77 + .../e2e/src/heloc/heloc-maximum.e2e-spec.ts | 177 + .../e2e/src/heloc/heloc-minimal.e2e-spec.ts | 99 + .../e2e/src/heloc/pages/page-one.po.ts | 56 + .../pages/select-loan-credit.po.ts | 10 + .../select-loan/pages/select-loan-home.po.ts | 11 + .../pages/select-loan-personal.po.ts | 7 + .../pages/select-loan-vehicle-sub.po.ts | 9 + .../pages/select-loan-vehicle.po.ts | 11 + .../src/select-loan/pages/select-loan.po.ts | 42 + .../src/select-loan/select-loan.e2e-spec.ts | 107 + .../shared/data-driven/test-input-command.ts | 12 + .../e2e/src/shared/page-data-tools.ts | 22 + .../e2e/src/shared/page-object-tools.ts | 87 + .../src/shared/pages/collateral-page.po.ts | 88 + .../e2e/src/shared/pages/contact-page.po.ts | 125 + .../src/shared/pages/employment-page.po.ts | 137 + .../e2e/src/shared/pages/hmda-page.po.ts | 122 + .../e2e/src/shared/pages/residence-page.po.ts | 143 + .../src/shared/pages/spouse-type-page.po.ts | 41 + .../e2e/src/shared/pages/summary-page.po.ts | 34 + .../src/shared/pages/tattle-spouse-page.po.ts | 75 + .../pages/vehicle-collateral-page.po.ts | 93 + .../e2e/src/shared/pages/wizard-page.po.ts | 35 + .../e2e/src/shared/shared-app.po.ts | 91 + .../signature/pages/page-disclosures.po.ts | 34 + .../e2e/src/signature/pages/page-one.po.ts | 92 + .../signature-full-spouses.e2e-spec.ts | 156 + .../signature/signature-go-back.e2e-spec.ts | 127 + .../signature/signature-maximum.e2e-spec.ts | 141 + .../signature/signature-minimal.e2e-spec.ts | 78 + .../signature-tattle-spouses.e2e-spec.ts | 132 + .../e2e/src/vehicle/pages/page-one.po.ts | 77 + .../src/vehicle/vehicle-maximum.e2e-spec.ts | 152 + .../src/vehicle/vehicle-minimal.e2e-spec.ts | 96 + .../e2e/tsconfig.e2e.json | 13 + .../kustomize/base/Deployment.yaml | 44 + .../kustomize/base/Service.yaml | 11 + .../kustomize/base/kustomization.yaml | 8 + .../kustomize/base/virtual-service.yaml | 19 + .../overlays/local/common/kustomization.yaml | 7 + .../common/set_appsettings_deployment.yaml | 12 + .../overlays/local/kustomization.yaml | 2 + .../overlays/local/normal/configmap.yaml | 17 + .../overlays/local/normal/kustomization.yaml | 5 + .../local/normal/set_virtual_service.yaml | 8 + .../overlays/prd/common/kustomization.yaml | 7 + .../common/set_appsettings_deployment.yaml | 13 + .../kustomize/overlays/prd/kustomization.yaml | 2 + .../overlays/prd/normal/configmap.yaml | 16 + .../overlays/prd/normal/kustomization.yaml | 5 + .../prd/normal/set_virtual_service.yaml | 18 + .../overlays/qa/common/kustomization.yaml | 7 + .../qa/common/set_appsettings_deployment.yaml | 12 + .../kustomize/overlays/qa/kustomization.yaml | 2 + .../overlays/qa/normal/configmap.yaml | 17 + .../overlays/qa/normal/kustomization.yaml | 5 + .../qa/normal/set_virtual_service.yaml | 18 + .../overlays/uat/common/kustomization.yaml | 7 + .../common/set_appsettings_deployment.yaml | 12 + .../kustomize/overlays/uat/kustomization.yaml | 2 + .../overlays/uat/normal/configmap.yaml | 17 + .../overlays/uat/normal/kustomization.yaml | 5 + .../uat/normal/set_virtual_service.yaml | 18 + .../package-lock.json | 30466 ++++++++++++++++ .../package.json | 106 + .../setup-env.ps1 | 44 + .../src/Robots.txt | 2 + .../src/_variables.scss | 6 + .../src/app/admin/admin-groups.ts | 30 + .../src/app/admin/admin-routing.module.ts | 31 + .../src/app/admin/admin.component.html | 7 + .../src/app/admin/admin.component.scss | 0 .../src/app/admin/admin.component.spec.ts | 0 .../src/app/admin/admin.component.ts | 11 + .../src/app/admin/admin.module.ts | 68 + .../branch-config.component.html | 81 + .../branch-config.component.scss | 38 + .../branch-config.component.spec.ts | 66 + .../branch-config/branch-config.component.ts | 126 + .../branch-config.service.spec.ts | 175 + .../branch-config/branch-config.service.ts | 160 + .../app/admin/branch-config/branch-config.ts | 10 + .../branch-name-config.component.html | 41 + .../branch-name-config.component.scss | 38 + .../branch-name-config.component.spec.ts | 66 + .../branch-name-config.component.ts | 65 + .../branch-name-config.service.spec.ts | 106 + .../branch-name-config.service.ts | 51 + .../branch-name-config/branch-name-config.ts | 10 + .../src/app/admin/cards/card.component.scss | 71 + .../cards/main-card/main-card.component.html | 30 + .../cards/main-card/main-card.component.scss | 1 + .../main-card/main-card.component.spec.ts | 45 + .../cards/main-card/main-card.component.ts | 20 + .../src/app/admin/filter/filter-api.ts | 11 + .../src/app/admin/filter/filter-field.ts | 19 + .../src/app/admin/filter/filter-util.ts | 90 + .../src/app/admin/filter/filter.ts | 13 + .../src/app/admin/filter/status.ts | 9 + .../landing/admin-landing.component.html | 24 + .../landing/admin-landing.component.scss | 22 + .../landing/admin-landing.component.spec.ts | 37 + .../admin/landing/admin-landing.component.ts | 10 + .../admin/loan-data/loan-data.component.html | 3 + .../admin/loan-data/loan-data.component.scss | 0 .../loan-data/loan-data.component.spec.ts | 39 + .../admin/loan-data/loan-data.component.ts | 37 + .../loan-table/loan-table.component.html | 103 + .../loan-table/loan-table.component.scss | 55 + .../loan-table/loan-table.component.spec.ts | 58 + .../admin/loan-table/loan-table.component.ts | 195 + .../loan-table/loan-table.service.spec.ts | 131 + .../admin/loan-table/loan-table.service.ts | 135 + .../src/app/admin/loan-table/loan-table.ts | 10 + .../navigation/top-navigation.component.html | 2 + .../navigation/top-navigation.component.scss | 11 + .../top-navigation.component.spec.ts | 35 + .../navigation/top-navigation.component.ts | 15 + .../schumer-preview.component.html | 20 + .../schumer-preview.component.scss | 11 + .../schumer-preview.component.ts | 18 + .../app/admin/snack-bar-error/server-error.ts | 11 + .../snack-bar-error.component.html | 4 + .../snack-bar-error.component.scss | 0 .../snack-bar-error.component.ts | 21 + .../snack-bar-error/snack-bar.service.ts | 35 + .../system-config.component.html | 25 + .../system-config.component.scss | 16 + .../system-config/system-config.component.ts | 71 + .../system-config/system-config.service.ts | 53 + .../src/app/app-routing.module.ts | 125 + .../src/app/app.component.html | 9 + .../src/app/app.component.scss | 0 .../src/app/app.component.spec.ts | 69 + .../src/app/app.component.ts | 39 + .../src/app/app.module.ts | 281 + .../decision-animation.component.html | 9 + .../decision-animation.component.scss | 0 .../decision-animation.component.spec.ts | 67 + .../decision-animation.component.ts | 15 + .../decision-hero.component.html | 5 + .../decision-hero.component.scss | 0 .../decision-hero.component.spec.ts | 73 + .../decision-hero/decision-hero.component.ts | 62 + .../app/layout/footer/footer.component.html | 27 + .../app/layout/footer/footer.component.scss | 0 .../layout/footer/footer.component.spec.ts | 29 + .../src/app/layout/footer/footer.component.ts | 16 + .../layout/header/header-icon.component.html | 7 + .../header/header-icon.component.spec.ts | 27 + .../layout/header/header-icon.component.ts | 12 + .../app/layout/header/header.component.html | 34 + .../app/layout/header/header.component.scss | 53 + .../layout/header/header.component.spec.ts | 121 + .../src/app/layout/header/header.component.ts | 29 + .../app/layout/header/header.service.spec.ts | 61 + .../src/app/layout/header/header.service.ts | 19 + .../layout/waypoints/waypoint.service.spec.ts | 106 + .../app/layout/waypoints/waypoint.service.ts | 162 + .../src/app/layout/waypoints/waypoint.ts | 11 + .../layout/waypoints/waypoints.component.html | 26 + .../layout/waypoints/waypoints.component.scss | 177 + .../waypoints/waypoints.component.spec.ts | 72 + .../layout/waypoints/waypoints.component.ts | 30 + .../src/app/loan-wizard/application-page.ts | 35 + .../application-wizard.service.spec.ts | 236 + .../loan-wizard/application-wizard.service.ts | 353 + .../error-display.component.html | 12 + .../error-display.component.scss | 5 + .../error-display.component.spec.ts | 39 + .../error-display/error-display.component.ts | 54 + .../error-display.service.spec.ts | 53 + .../error-display/error-display.service.ts | 128 + .../input-icon/input-icon.component.html | 6 + .../input-icon/input-icon.component.scss | 45 + .../input-icon/input-icon.component.spec.ts | 38 + .../input-icon/input-icon.component.ts | 18 + .../address-compare-info-form.component.html | 103 + ...ddress-compare-info-form.component.spec.ts | 157 + .../address-compare-info-form.component.ts | 145 + .../address-compare-info-form.service.spec.ts | 38 + .../address-compare-info-form.service.ts | 41 + .../address-compare-information.ts | 17 + .../address-compare/address-compare.scss | 80 + .../address/address-info-form.component.html | 82 + .../address-info-form.component.spec.ts | 112 + .../address/address-info-form.component.ts | 30 + .../address/address-information.ts | 8 + .../is-address-same-info-form.component.html | 20 + ...s-address-same-info-form.component.spec.ts | 90 + .../is-address-same-info-form.component.ts | 25 + .../address/is-address-same-information.ts | 3 + .../residence-info-form.component.html | 87 + .../residence-info-form.component.spec.ts | 49 + .../address/residence-info-form.component.ts | 35 + .../address/residence-information.ts | 7 + .../loan-wizard/field-groups/common-groups.ts | 533 + ...credit-card-disclosure-form.component.html | 25 + ...credit-card-disclosure-form.component.scss | 2 + ...dit-card-disclosure-form.component.spec.ts | 57 + .../credit-card-disclosure-form.component.ts | 13 + .../disclosures/credit-card-disclosure.ts | 5 + .../electronic-disclosures-agreement.html | 43 + ...ctronic-disclosures-form.component.spec.ts | 41 + .../electronic-disclosures-form.component.ts | 19 + .../alimony-info-form.component.html | 19 + .../alimony-info-form.component.spec.ts | 59 + .../employment/alimony-info-form.component.ts | 17 + .../employment/alimony-information.ts | 3 + .../employment-info-form.component.html | 150 + .../employment-info-form.component.spec.ts | 83 + .../employment-info-form.component.ts | 88 + .../employment/employment-information.ts | 15 + .../employment/financial-information.ts | 5 + .../alimony-flag-form.component.html | 10 + .../alimony-flag-form.component.spec.ts | 60 + .../financial/alimony-flag-form.component.ts | 15 + .../employment-flag-form.component.html | 10 + .../employment-flag-form.component.spec.ts | 60 + .../employment-flag-form.component.ts | 19 + .../other-income-flag-form.component.html | 10 + .../other-income-flag-form.component.spec.ts | 60 + .../other-income-flag-form.component.ts | 21 + .../employment/income-information.ts | 7 + .../other-income-info-form.component.html | 51 + .../other-income-info-form.component.spec.ts | 61 + .../other-income-info-form.component.ts | 59 + .../collateral-info-form.component.html | 182 + .../collateral-info-form.component.spec.ts | 137 + .../equity/collateral-info-form.component.ts | 125 + .../collateral-info-form.service.spec.ts | 66 + .../equity/collateral-info-form.service.ts | 37 + .../equity/collateral-information.ts | 14 + .../equity/equity-member-information.ts | 10 + .../is-rented-lot-info-form.component.html | 49 + .../is-rented-lot-info-form.component.spec.ts | 85 + .../is-rented-lot-info-form.component.ts | 44 + .../hmda/ethnicity-info-form.component.html | 66 + .../ethnicity-info-form.component.spec.ts | 78 + .../hmda/ethnicity-info-form.component.ts | 17 + .../hmda/ethnicity-information.ts | 10 + .../hmda/gender-info-form.component.html | 25 + .../hmda/gender-info-form.component.spec.ts | 72 + .../hmda/gender-info-form.component.ts | 17 + .../field-groups/hmda/gender-information.ts | 5 + .../field-groups/hmda/hmda-styles.scss | 3 + .../hmda/race-info-form.component.html | 157 + .../hmda/race-info-form.component.spec.ts | 86 + .../hmda/race-info-form.component.ts | 17 + .../field-groups/hmda/race-information.ts | 22 + .../member/member-info-form.component.html | 169 + .../member/member-info-form.component.spec.ts | 183 + .../member/member-info-form.component.ts | 100 + .../member/member-info-form.service.spec.ts | 113 + .../member/member-info-form.service.ts | 82 + .../member/membership-information.ts | 13 + .../personal-info-form.component.html | 286 + .../personal-info-form.component.spec.ts | 105 + .../personal/personal-info-form.component.ts | 84 + .../personal/personal-information.ts | 22 + ...-full-info-spouse-info-form.component.html | 20 + ...ll-info-spouse-info-form.component.spec.ts | 90 + ...is-full-info-spouse-info-form.component.ts | 15 + .../is-full-info-spouse-information.ts | 3 + .../additional-info-form.component.html | 20 + .../additional-info-form.component.spec.ts | 71 + .../summary/additional-info-form.component.ts | 17 + .../field-groups/summary/additional-info.ts | 3 + .../summary/note-form.component.html | 51 + .../summary/note-form.component.spec.ts | 71 + .../summary/note-form.component.ts | 17 + ...e-spouse-personal-info-form.component.html | 37 + ...pouse-personal-info-form.component.spec.ts | 88 + ...tle-spouse-personal-info-form.component.ts | 22 + .../tattle-spouse-personal-information.ts | 8 + ...ehicle-collateral-info-form.component.html | 536 + ...cle-collateral-info-form.component.spec.ts | 236 + .../vehicle-collateral-info-form.component.ts | 299 + ...hicle-collateral-info-form.service.spec.ts | 106 + .../vehicle-collateral-info-form.service.ts | 94 + .../vehicle/vehicle-collateral-information.ts | 44 + ...oint-credit-confirmation.component.spec.ts | 53 + .../joint-credit-confirmation.component.ts | 64 + .../joint-credit-confirmation.html | 16 + .../field-groups/wizard-validators.ts | 330 + .../loan-wizard/loan-wizard.component.html | 16 + .../loan-wizard/loan-wizard.component.scss | 0 .../loan-wizard/loan-wizard.component.spec.ts | 53 + .../app/loan-wizard/loan-wizard.component.ts | 10 + .../address/address-info-page.component.html | 59 + .../address-info-page.component.spec.ts | 545 + .../address/address-info-page.component.ts | 343 + .../address/address-info-page.service.spec.ts | 171 + .../address/address-info-page.service.ts | 266 + .../co-borrower-address-info-page.spec.ts | 61 + .../pages/co-borrower-address-info-page.ts | 14 + ...-borrower-spouse-address-info-page.spec.ts | 70 + .../co-borrower-spouse-address-info-page.ts | 19 + .../pages/primary-address-info-page.spec.ts | 63 + .../pages/primary-address-info-page.ts | 33 + .../primary-spouse-address-info-page.spec.ts | 69 + .../pages/primary-spouse-address-info-page.ts | 19 + .../decision/decision-page.component.html | 20 + .../decision/decision-page.component.scss | 1 + .../decision/decision-page.component.spec.ts | 52 + .../decision/decision-page.component.ts | 46 + .../decision/decision-page.service.spec.ts | 93 + .../common/decision/decision-page.service.ts | 44 + .../common/decision/decision-page.spec.ts | 53 + .../wizards/common/decision/decision-page.ts | 15 + .../employment-info-page.component.html | 49 + .../employment-info-page.component.spec.ts | 528 + .../employment-info-page.component.ts | 338 + .../employment-info-page.service.spec.ts | 127 + .../employment-info-page.service.ts | 248 + .../co-borrower-employment-info-page.spec.ts | 60 + .../pages/co-borrower-employment-info-page.ts | 15 + ...rrower-spouse-employment-info-page.spec.ts | 68 + ...co-borrower-spouse-employment-info-page.ts | 18 + .../primary-employment-info-page.spec.ts | 62 + .../pages/primary-employment-info-page.ts | 54 + ...rimary-spouse-employment-info-page.spec.ts | 68 + .../primary-spouse-employment-info-page.ts | 19 + .../common/hmda/hmda-info-page.component.html | 37 + .../hmda/hmda-info-page.component.spec.ts | 141 + .../common/hmda/hmda-info-page.component.ts | 82 + .../hmda/hmda-info-page.service.spec.ts | 111 + .../common/hmda/hmda-info-page.service.ts | 135 + .../pages/co-borrower-hmda-info-page.spec.ts | 74 + .../hmda/pages/co-borrower-hmda-info-page.ts | 22 + .../hmda/pages/primary-hmda-info-page.spec.ts | 71 + .../hmda/pages/primary-hmda-info-page.ts | 22 + .../member/member-info-page.component.html | 36 + .../member/member-info-page.component.scss | 7 + .../member/member-info-page.component.spec.ts | 240 + .../member/member-info-page.component.ts | 158 + .../member/member-info-page.service.spec.ts | 102 + .../common/member/member-info-page.service.ts | 134 + .../wizards/common/member/member-info-page.ts | 22 + .../co-borrower-personal-info-page.spec.ts | 61 + .../pages/co-borrower-personal-info-page.ts | 15 + ...borrower-spouse-personal-info-page.spec.ts | 68 + .../co-borrower-spouse-personal-info-page.ts | 18 + .../pages/primary-personal-info-page.spec.ts | 63 + .../pages/primary-personal-info-page.ts | 30 + .../primary-spouse-personal-info-page.spec.ts | 68 + .../primary-spouse-personal-info-page.ts | 19 + .../personal-info-page.component.html | 29 + .../personal-info-page.component.spec.ts | 206 + .../personal/personal-info-page.component.ts | 126 + .../personal-info-page.service.spec.ts | 121 + .../personal/personal-info-page.service.ts | 152 + .../processing/processing-page.component.html | 19 + .../processing/processing-page.component.scss | 25 + .../processing-page.component.spec.ts | 127 + .../processing/processing-page.component.ts | 128 + .../processing-page.service.spec.ts | 139 + .../processing/processing-page.service.ts | 111 + .../common/processing/processing-page.spec.ts | 51 + .../common/processing/processing-page.ts | 15 + .../common/processing/processing-step.ts | 33 + .../spouse-type-info-page.component.html | 16 + .../spouse-type-info-page.component.spec.ts | 124 + .../spouse-type-info-page.component.ts | 75 + .../spouse-type-info-page.service.spec.ts | 113 + .../spouse-type-info-page.service.ts | 104 + .../spouse-type/spouse-type-info-page.ts | 23 + .../application-summary.component.html | 20 + .../application-summary.component.spec.ts | 70 + .../summary/application-summary.component.ts | 71 + .../summary/collateral-summary.component.html | 55 + .../collateral-summary.component.spec.ts | 84 + .../summary/collateral-summary.component.ts | 40 + .../summary/income-summary.component.html | 64 + .../summary/income-summary.component.spec.ts | 92 + .../summary/income-summary.component.ts | 35 + .../summary/personal-summary.component.html | 30 + .../personal-summary.component.spec.ts | 73 + .../summary/personal-summary.component.ts | 30 + .../summary/residence-summary.component.html | 54 + .../residence-summary.component.spec.ts | 75 + .../summary/residence-summary.component.ts | 49 + .../common/summary/summary-info-page.spec.ts | 50 + .../common/summary/summary-info-page.ts | 15 + .../summary/summary-page.component.html | 197 + .../summary/summary-page.component.scss | 3 + .../summary/summary-page.component.spec.ts | 120 + .../common/summary/summary-page.component.ts | 115 + .../summary/summary-page.service.spec.ts | 80 + .../common/summary/summary-page.service.ts | 139 + .../tattle-spouse-summary.component.html | 14 + .../tattle-spouse-summary.component.spec.ts | 57 + .../tattle-spouse-summary.component.ts | 25 + .../summary/vehicle-summary.component.html | 110 + .../summary/vehicle-summary.component.spec.ts | 88 + .../summary/vehicle-summary.component.ts | 63 + ...o-borrower-tattle-spouse-info-page.spec.ts | 68 + .../co-borrower-tattle-spouse-info-page.ts | 15 + .../primary-tattle-spouse-info-page.spec.ts | 93 + .../pages/primary-tattle-spouse-info-page.ts | 44 + .../tattle-spouse-info-page.component.html | 35 + .../tattle-spouse-info-page.component.spec.ts | 153 + .../tattle-spouse-info-page.component.ts | 172 + .../tattle-spouse-info-page.service.spec.ts | 105 + .../tattle-spouse-info-page.service.ts | 139 + .../credit-card-disclosures-info-page.ts | 24 + ...redit-card-disclosures-page.component.html | 31 + ...it-card-disclosures-page.component.spec.ts | 85 + .../credit-card-disclosures-page.component.ts | 58 + ...edit-card-disclosures-page.service.spec.ts | 58 + .../credit-card-disclosures-page.service.ts | 105 + .../credit-card/credit-card-outline.ts | 144 + .../credit-card/credit-card-routing.module.ts | 64 + .../credit-card-schumer-box.component.html | 1 + .../credit-card-schumer-box.component.spec.ts | 49 + .../credit-card-schumer-box.component.ts | 23 + .../collateral-info-page.component.html | 43 + .../collateral-info-page.component.spec.ts | 457 + .../equity/collateral-info-page.component.ts | 211 + .../collateral-info-page.service.spec.ts | 178 + .../equity/collateral-info-page.service.ts | 261 + .../wizards/equity/collateral-info-page.ts | 22 + .../equity-member-info-page.component.html | 34 + .../equity-member-info-page.component.spec.ts | 195 + .../equity-member-info-page.component.ts | 146 + .../equity-member-info-page.service.spec.ts | 149 + .../equity/equity-member-info-page.service.ts | 189 + .../wizards/equity/equity-member-info-page.ts | 28 + .../wizards/equity/equity-outline.ts | 166 + .../wizards/equity/equity-routing.module.ts | 71 + .../app/loan-wizard/wizards/page-service.ts | 7 + .../signature/signature-loan-outline.ts | 138 + .../signature/signature-routing.module.ts | 64 + ...ehicle-collateral-info-page.component.html | 30 + ...cle-collateral-info-page.component.spec.ts | 97 + .../vehicle-collateral-info-page.component.ts | 161 + ...hicle-collateral-info-page.service.spec.ts | 140 + .../vehicle-collateral-info-page.service.ts | 207 + .../vehicle-collateral-info-page.spec.ts | 46 + .../vehicle/vehicle-collateral-info-page.ts | 25 + .../wizards/vehicle/vehicle-outline.ts | 149 + .../wizards/vehicle/vehicle-routing.module.ts | 66 + .../src/app/member/member-core.resolver.ts | 31 + .../member-login/member-login.component.html | 54 + .../member-login/member-login.component.scss | 51 + .../member-login.component.spec.ts | 145 + .../member-login/member-login.component.ts | 142 + .../member-login/member-login.service.spec.ts | 49 + .../member-login/member-login.service.ts | 32 + .../member-outlet.component.html | 1 + .../member-outlet.component.scss | 0 .../member-outlet.component.spec.ts | 36 + .../member-outlet/member-outlet.component.ts | 10 + .../member-verification.component.html | 56 + .../member-verification.component.scss | 36 + .../member-verification.component.spec.ts | 100 + .../member-verification.component.ts | 155 + .../member-verification.service.spec.ts | 51 + .../member-verification.service.ts | 32 + .../src/app/member/member.service.spec.ts | 107 + .../src/app/member/member.service.ts | 78 + .../src/app/member/models/core-address.ts | 21 + .../src/app/member/models/core-individual.ts | 38 + .../src/app/member/models/core-phone.ts | 11 + .../member/models/individual-credentials.ts | 10 + .../src/app/member/models/login-status.ts | 13 + .../app/member/models/member-credentials.ts | 10 + .../src/app/member/models/member-meta.ts | 12 + .../app/select-loan/cards/card.component.scss | 102 + .../cards/main-card/main-card.component.html | 30 + .../cards/main-card/main-card.component.scss | 1 + .../main-card/main-card.component.spec.ts | 45 + .../cards/main-card/main-card.component.ts | 20 + .../cards/sub-card/sub-card.component.html | 20 + .../cards/sub-card/sub-card.component.scss | 1 + .../cards/sub-card/sub-card.component.spec.ts | 35 + .../cards/sub-card/sub-card.component.ts | 16 + .../select-credit-card.component.html | 21 + .../select-credit-card.component.spec.ts | 37 + .../select-credit-card.component.ts | 11 + .../home/select-home-equity.component.html | 25 + .../home/select-home-equity.component.spec.ts | 37 + .../home/select-home-equity.component.ts | 11 + .../outlet/select-loan-outlet.component.html | 1 + .../select-loan-outlet.component.spec.ts | 37 + .../outlet/select-loan-outlet.component.ts | 10 + .../select-loan/select-loan.component.html | 24 + .../select-loan/select-loan.component.scss | 35 + .../select-loan/select-loan.component.spec.ts | 50 + .../app/select-loan/select-loan.component.ts | 13 + .../vehicle/select-vehicle.component.html | 21 + .../vehicle/select-vehicle.component.spec.ts | 37 + .../vehicle/select-vehicle.component.ts | 11 + .../select-sub-vehicle.component.html | 13 + .../select-sub-vehicle.component.spec.ts | 37 + .../select-sub-vehicle.component.ts | 11 + .../address-form-builder.service.spec.ts | 42 + .../address-form-builder.service.ts | 26 + ...ddress-validation-format-converter.spec.ts | 93 + .../address-validation-format-converter.ts | 67 + .../address-validation.service.ts | 21 + .../src/app/shared/api-models/api-address.ts | 11 + .../src/app/shared/api-models/api-employer.ts | 15 + .../src/app/shared/api-models/api-enums.ts | 41 + .../src/app/shared/api-models/api-error.ts | 12 + .../app/shared/api-models/api-hmda-enums.ts | 35 + .../src/app/shared/api-models/api-hmda.ts | 11 + .../src/app/shared/api-models/api-income.ts | 13 + .../app/shared/api-models/api-individual.ts | 45 + .../shared/api-models/api-lien-position.ts | 6 + .../shared/api-models/api-loan-application.ts | 30 + .../shared/api-models/api-loan-container.ts | 21 + .../src/app/shared/api-models/api-lot.ts | 7 + .../api-models/api-manufactured-home.ts | 12 + .../src/app/shared/api-models/api-page.ts | 32 + .../app/shared/api-models/api-real-estate.ts | 21 + .../api-models/api-residence-address.ts | 7 + .../src/app/shared/api-models/api-status.ts | 10 + .../src/app/shared/api-models/api-suffix.ts | 10 + .../shared/api-models/api-tattle-spouse.ts | 13 + .../src/app/shared/api-models/api-vehicle.ts | 48 + .../api-translator.service.spec.ts | 164 + .../api-translator/api-translator.service.ts | 80 + .../elements/address-translator.ts | 19 + .../elements/elements-translator.spec.ts | 258 + .../elements/employer-translator.ts | 26 + .../elements/hmda-translator.ts | 64 + .../elements/income-alimony-translator.ts | 19 + .../elements/income-employer-translator.ts | 27 + .../elements/income-other-translator.ts | 17 + .../elements/personal-translator.ts | 36 + .../elements/residence-translator.ts | 18 + .../elements/tattle-spouse-translator.ts | 25 + .../api-translator/header-translator.ts | 11 + .../individual-translator.spec.ts | 462 + .../api-translator/individual-translator.ts | 294 + .../credit-card-translator.spec.ts | 99 + .../credit-card-translator.ts | 32 + .../equity-translator.spec.ts | 247 + .../equity-translator.ts | 100 + .../signature-loan-translator.spec.ts | 107 + .../signature-loan-translator.ts | 34 + .../vehicle-translator.spec.ts | 180 + .../vehicle-translator.ts | 108 + .../app-down/app-down-page.component.html | 12 + .../app-down/app-down-page.component.scss | 18 + .../app-down/app-down-page.component.spec.ts | 32 + .../app-down/app-down-page.component.ts | 17 + .../app-loader-translator.service.spec.ts | 95 + .../app-loader-translator.service.ts | 385 + .../app-loader/app-loader.component.html | 19 + .../app-loader/app-loader.component.scss | 41 + .../app-loader/app-loader.component.spec.ts | 81 + .../shared/app-loader/app-loader.component.ts | 58 + .../app-loader/app-loader.service.spec.ts | 55 + .../shared/app-loader/app-loader.service.ts | 49 + .../app-unavailable-page.component.html | 5 + .../app-unavailable-page.component.spec.ts | 32 + .../app-unavailable-page.component.ts | 11 + .../before-you-begin.component.html | 11 + .../before-you-begin.component.spec.ts | 80 + .../before-you-begin.component.ts | 19 + .../src/app/shared/borrower-type.ts | 7 + .../configuration/configuration.resolver.ts | 41 + .../configuration.service.spec.ts | 92 + .../configuration/configuration.service.ts | 73 + .../app/shared/configuration/configuration.ts | 6 + .../configuration/default-settings.mock.ts | 1102 + .../app/shared/configuration/ui-settings.ts | 117 + .../src/app/shared/cookie/cookie-util.spec.ts | 33 + .../src/app/shared/cookie/cookie-util.ts | 61 + .../core-translator.service.spec.ts | 104 + .../core-translator.service.ts | 117 + .../credit-application-dao.service.spec.ts | 126 + .../shared/credit-application-dao.service.ts | 197 + .../app/shared/credit-application-values.ts | 9 + .../shared/credit-application.service.spec.ts | 422 + .../app/shared/credit-application.service.ts | 871 + .../src/app/shared/credit-application.ts | 95 + .../src/app/shared/data-table.service.spec.ts | 53 + .../src/app/shared/data-table.service.ts | 97 + .../src/app/shared/date/date-builder.ts | 44 + .../dealership-portal-page.component.html | 186 + .../dealership-portal-page.component.ts | 207 + .../dealership-portal-page.service.ts | 103 + .../app/shared/decision/decision-status.ts | 26 + .../shared/decision/decision.service.spec.ts | 68 + .../app/shared/decision/decision.service.ts | 71 + .../app/shared/export/export.service.spec.ts | 97 + .../src/app/shared/export/export.service.ts | 72 + .../file-upload/file-upload.component.html | 48 + .../file-upload/file-upload.component.scss | 95 + .../file-upload/file-upload.component.spec.ts | 104 + .../file-upload/file-upload.component.ts | 268 + .../file-upload/file-upload.mock.service.ts | 32 + .../shared/file-upload/file-upload.service.ts | 133 + .../shared/guards/admin-role.guard.spec.ts | 65 + .../src/app/shared/guards/admin-role.guard.ts | 27 + .../shared/guards/app-timeout.guard.spec.ts | 36 + .../app/shared/guards/app-timeout.guard.ts | 23 + .../shared/guards/guard-manager.guard.spec.ts | 48 + .../app/shared/guards/guard-manager.guard.ts | 44 + .../shared/guards/loan-route.guard.spec.ts | 43 + .../src/app/shared/guards/loan-route.guard.ts | 23 + .../shared/guards/member-login.guard.spec.ts | 61 + .../app/shared/guards/member-login.guard.ts | 29 + .../guards/member-verification-guard.ts | 40 + .../guards/member-verification.guard.spec.ts | 59 + .../guards/stepper-controller-guard.spec.ts | 67 + .../shared/guards/stepper-controller-guard.ts | 36 + .../src/app/shared/guards/test-role.guard.ts | 27 + .../shared/guards/tester-role.guard.spec.ts | 65 + .../app/shared/http-error-reporter.service.ts | 33 + .../income-limit/income-limits-information.ts | 4 + .../income-limits.service.spec.ts | 92 + .../income-limit/income-limits.service.ts | 64 + .../disclosure-boxes.component.html | 45 + .../disclosure-boxes.component.spec.ts | 42 + .../disclosure-boxes.component.ts | 58 + .../shared/legal-message/tcpa.component.html | 1 + .../shared/legal-message/tcpa.component.scss | 3 + .../legal-message/tcpa.component.spec.ts | 35 + .../shared/legal-message/tcpa.component.ts | 12 + .../src/app/shared/loan-limit/loan-limit.ts | 4 + .../loan-limit/loan-limits.service.spec.ts | 105 + .../shared/loan-limit/loan-limits.service.ts | 55 + .../src/app/shared/loan-type.ts | 314 + .../src/app/shared/loan-type/loan-category.ts | 6 + .../src/app/shared/loan-type/loan-code.ts | 19 + .../src/app/shared/loan-type/loan-purpose.ts | 19 + .../src/app/shared/loan-type/loan-title.ts | 18 + .../shared/masking/masking.service.spec.ts | 85 + .../src/app/shared/masking/masking.service.ts | 68 + .../app/shared/options/branch-options.spec.ts | 36 + .../src/app/shared/options/branch-options.ts | 35 + .../app/shared/options/route-options.spec.ts | 13 + .../src/app/shared/options/route-options.ts | 25 + .../src/app/shared/options/select-options.ts | 138 + .../app/shared/options/term-options.spec.ts | 58 + .../src/app/shared/options/term-options.ts | 45 + .../shared/options/vehicle-options.spec.ts | 29 + .../src/app/shared/options/vehicle-options.ts | 75 + .../shared/page-focus/auto-focus.directive.ts | 26 + .../patriot-act/patriot-act.component.html | 14 + .../patriot-act/patriot-act.component.scss | 0 .../patriot-act/patriot-act.component.spec.ts | 27 + .../patriot-act/patriot-act.component.ts | 17 + .../app/shared/pipes/address-status.pipe.ts | 11 + .../app/shared/pipes/boolean-string.pipe.ts | 11 + .../app/shared/pipes/employment-type.pipe.ts | 11 + .../app/shared/pipes/format.service.spec.ts | 239 + .../src/app/shared/pipes/format.service.ts | 276 + .../src/app/shared/pipes/loan-code.pipe.ts | 12 + .../src/app/shared/pipes/loan-icon.pipe.ts | 12 + .../app/shared/pipes/occupancy-type.pipe.ts | 11 + .../src/app/shared/pipes/phone-number.pipe.ts | 11 + .../src/app/shared/pipes/phone-type.pipe.ts | 11 + .../app/shared/pipes/property-type.pipe.ts | 20 + .../src/app/shared/pipes/ssn.pipe.ts | 11 + .../src/app/shared/pipes/state.pipe.ts | 11 + .../app/shared/pipes/uploaded-files.pipe.ts | 11 + .../shared/pipes/vehicle-collateral.pipe.ts | 11 + .../src/app/shared/pipes/vehicle-type.pipe.ts | 11 + .../schumer-boxes/schumer-box.component.html | 1196 + .../schumer-boxes/schumer-box.component.scss | 52 + .../schumer-box.component.spec.ts | 40 + .../schumer-boxes/schumer-box.component.ts | 147 + .../src/app/shared/security.service.spec.ts | 76 + .../src/app/shared/security.service.ts | 50 + .../src/app/shared/shared.module.ts | 12 + .../terms/available-terms-calculator.ts | 5 + .../terms/default-term-calculator.spec.ts | 59 + .../shared/terms/default-term-calculator.ts | 50 + .../src/app/shared/terms/term-input-fields.ts | 18 + .../terms/vehicle-term-calculator.spec.ts | 157 + .../shared/terms/vehicle-term-calculator.ts | 66 + .../timeout-landing-page.component.html | 8 + .../timeout-landing-page.component.spec.ts | 52 + .../timeout/timeout-landing-page.component.ts | 34 + .../timeout/timeout-popup.component.html | 18 + .../timeout/timeout-popup.component.spec.ts | 192 + .../shared/timeout/timeout-popup.component.ts | 99 + .../timeout/timeout-popup.service.spec.ts | 90 + .../shared/timeout/timeout-popup.service.ts | 103 + .../src/app/shared/vehicle-type/motor-type.ts | 6 + .../app/shared/vehicle-type/trailer-type.ts | 7 + .../app/shared/vehicle-type/vehicle-type.ts | 12 + .../src/app/shared/window.service.spec.ts | 35 + .../src/app/shared/window.service.ts | 42 + .../shared-mocks/activated-route.mock.ts | 8 + .../shared-mocks/api-translator-pre-post.ts | 348 + .../shared-mocks/broken-applications.ts | 216 + .../configuration.mock.service.ts | 40 + .../credit-application-dao.mock.service.ts | 38 + .../shared-mocks/loan-table.mock.service.ts | 81 + .../test-api-credit-application.ts | 168 + .../shared-mocks/test-api-loan-container.ts | 156 + .../shared-mocks/test-individual.mock.ts | 357 + .../vehicle-credit-application.mock.ts | 218 + .../src/assets/.gitkeep | 0 .../src/assets/LM_Full-Logo_Main.png | Bin 0 -> 7863 bytes .../src/assets/cc-images/LCU_cashback.png | Bin 0 -> 40619 bytes .../src/assets/cc-images/LCU_rewards.png | Bin 0 -> 44936 bytes .../src/assets/cc-images/LCU_secured.png | Bin 0 -> 47873 bytes .../src/assets/cc-images/LCU_student.png | Bin 0 -> 48825 bytes .../src/assets/checkbox/check.png | Bin 0 -> 18151 bytes .../src/assets/eqo.png | Bin 0 -> 1112 bytes ...ERMS OF OUR HOME EQUITY LINE OF CREDIT.pdf | Bin 0 -> 295121 bytes ... Equity Lines of Credit Revised 8-2022.pdf | Bin 0 -> 401602 bytes .../src/assets/i18n/en.json | 1044 + .../src/assets/loading.gif | Bin 0 -> 62694 bytes .../src/assets/ncua.png | Bin 0 -> 36939 bytes .../src/assets/settings/default_settings.json | 2324 ++ .../waypoint/applicant-icon-blue-01.png | Bin 0 -> 4618 bytes .../waypoint/applicant-icon-gray-01.png | Bin 0 -> 2955 bytes .../assets/waypoint/collateral-icon-blue.png | Bin 0 -> 5821 bytes .../assets/waypoint/collateral-icon-gray.png | Bin 0 -> 3678 bytes .../assets/waypoint/completed-check-icon.png | Bin 0 -> 4298 bytes .../src/assets/waypoint/finish-icon-gray.png | Bin 0 -> 3394 bytes .../src/assets/waypoint/review-icon-blue.png | Bin 0 -> 5158 bytes .../src/assets/waypoint/review-icon-gray.png | Bin 0 -> 3316 bytes .../src/browserslist | 9 + .../src/environments/enivronment-common.ts | 2 + .../src/environments/environment.dev.ts | 33 + .../src/environments/environment.e2e.ts | 16 + .../src/environments/environment.local.ts | 19 + .../src/environments/environment.prod.ts | 19 + .../src/environments/environment.qa.ts | 19 + .../src/environments/environment.ts | 90 + .../src/environments/environment.uat.ts | 19 + .../src/favicon.ico | Bin 0 -> 2819 bytes .../src/index.html | 26 + .../src/karma.conf.js | 75 + .../src/main.ts | 12 + .../src/polyfills.ts | 80 + .../src/styles.scss | 455 + .../src/test.ts | 35 + .../src/tsconfig.app.json | 12 + .../src/tsconfig.spec.json | 19 + .../src/tslint.json | 17 + .../src/web.config | 62 + .../tsconfig.json | 20 + .../tslint.json | 130 + LCU.WebApps.External.OnlineLoanApp/version.js | 22 + .../wiki/.order | 2 + .../wiki/API-to-Storage-to-Blob-to-ACTion.md | 0 .../wiki/File-Upload-Component.md | 203 + .../wiki/file-upload-action.png | Bin 0 -> 20300 bytes .../wiki/file-upload-student.png | Bin 0 -> 10293 bytes 776 files changed, 86726 insertions(+) create mode 100644 LCU.WebApps.External.OnlineLoanApp/.editorconfig create mode 100644 LCU.WebApps.External.OnlineLoanApp/.gitignore create mode 100644 LCU.WebApps.External.OnlineLoanApp/CHANGELOG.md create mode 100644 LCU.WebApps.External.OnlineLoanApp/Dockerfile create mode 100644 LCU.WebApps.External.OnlineLoanApp/README.md create mode 100644 LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/install_dependencies.ps1 create mode 100644 LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/kind.cluster.config create mode 100644 LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/setup_and_deploy.ps1 create mode 100644 LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/teardown.ps1 create mode 100644 LCU.WebApps.External.OnlineLoanApp/angular.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-k8s-pipeline.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-feature-branches.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-prd.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-qa.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-uat.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/prd-pvars.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/qa-pvars.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/uat-pvars.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/docker/nginx.conf create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/protractor.conf.js create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-full-spouses.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-go-back.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-maximum.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-minimal.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-tattle-spouses.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/pages/page-disclosures.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/pages/page-one.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/heloc-maximum.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/heloc-minimal.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/pages/page-one.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-credit.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-home.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-personal.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-vehicle-sub.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-vehicle.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/select-loan.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/data-driven/test-input-command.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/page-data-tools.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/page-object-tools.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/collateral-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/contact-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/employment-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/hmda-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/residence-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/spouse-type-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/summary-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/tattle-spouse-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/vehicle-collateral-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/wizard-page.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/shared-app.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/pages/page-disclosures.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/pages/page-one.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-full-spouses.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-go-back.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-maximum.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-minimal.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-tattle-spouses.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/pages/page-one.po.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/vehicle-maximum.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/vehicle-minimal.e2e-spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/e2e/tsconfig.e2e.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/base/Deployment.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/base/Service.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/base/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/base/virtual-service.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/common/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/common/set_appsettings_deployment.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/configmap.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/set_virtual_service.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/common/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/common/set_appsettings_deployment.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/configmap.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/set_virtual_service.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/common/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/common/set_appsettings_deployment.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/configmap.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/set_virtual_service.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/common/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/common/set_appsettings_deployment.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/configmap.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/kustomization.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/set_virtual_service.yaml create mode 100644 LCU.WebApps.External.OnlineLoanApp/package-lock.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/package.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/setup-env.ps1 create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/Robots.txt create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/_variables.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin-groups.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin-routing.module.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.module.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/card.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-api.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-field.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-util.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/status.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/server-error.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/app-routing.module.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/app.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/app.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/app.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/app.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/app.module.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-wizard.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-wizard.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/common-groups.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-agreement.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/income-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/equity-member-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/hmda-styles.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/membership-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/wizard-validators.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-address-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-address-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-spouse-address-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-spouse-address-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-address-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-address-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-spouse-address-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-spouse-address-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-employment-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-employment-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-spouse-employment-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-spouse-employment-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-employment-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-employment-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-spouse-employment-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-spouse-employment-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/co-borrower-hmda-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/co-borrower-hmda-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/primary-hmda-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/primary-hmda-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-personal-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-personal-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-spouse-personal-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-spouse-personal-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-personal-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-personal-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-spouse-personal-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-spouse-personal-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-step.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/primary-tattle-spouse-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/primary-tattle-spouse-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-outline.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-routing.module.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-outline.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-routing.module.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/page-service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/signature/signature-loan-outline.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/signature/signature-routing.module.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-outline.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-routing.module.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-core.resolver.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/member.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-address.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-individual.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-phone.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/models/individual-credentials.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/models/login-status.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/models/member-credentials.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/member/models/member-meta.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/card.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-form-builder.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-form-builder.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation-format-converter.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation-format-converter.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-address.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-employer.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-enums.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-error.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-hmda-enums.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-hmda.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-income.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-individual.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-lien-position.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-loan-application.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-loan-container.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-lot.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-manufactured-home.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-page.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-real-estate.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-residence-address.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-status.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-suffix.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-tattle-spouse.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-vehicle.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/api-translator.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/api-translator.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/address-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/elements-translator.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/employer-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/hmda-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-alimony-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-employer-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-other-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/personal-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/residence-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/tattle-spouse-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/header-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/individual-translator.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/individual-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/credit-card-translator.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/credit-card-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/equity-translator.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/equity-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/signature-loan-translator.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/signature-loan-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/vehicle-translator.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/vehicle-translator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader-translator.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader-translator.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/borrower-type.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.resolver.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/default-settings.mock.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/ui-settings.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/cookie/cookie-util.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/cookie/cookie-util.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/core-translator/core-translator.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/core-translator/core-translator.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-dao.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-dao.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-values.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/data-table.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/data-table.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/date/date-builder.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision-status.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/export/export.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/export/export.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.mock.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/admin-role.guard.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/admin-role.guard.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/app-timeout.guard.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/app-timeout.guard.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/guard-manager.guard.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/guard-manager.guard.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/loan-route.guard.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/loan-route.guard.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-login.guard.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-login.guard.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-verification-guard.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-verification.guard.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/stepper-controller-guard.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/stepper-controller-guard.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/test-role.guard.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/tester-role.guard.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/http-error-reporter.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits-information.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limit.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limits.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limits.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-category.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-code.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-purpose.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-title.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/masking/masking.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/masking/masking.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/branch-options.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/branch-options.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/route-options.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/route-options.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/select-options.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/term-options.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/term-options.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/vehicle-options.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/vehicle-options.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/page-focus/auto-focus.directive.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/address-status.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/boolean-string.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/employment-type.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/format.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/format.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/loan-code.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/loan-icon.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/occupancy-type.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/phone-number.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/phone-type.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/property-type.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/ssn.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/state.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/uploaded-files.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/vehicle-collateral.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/vehicle-type.pipe.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/security.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/security.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/shared.module.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/available-terms-calculator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/default-term-calculator.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/default-term-calculator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/term-input-fields.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/vehicle-term-calculator.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/vehicle-term-calculator.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/motor-type.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/trailer-type.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/vehicle-type.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/window.service.spec.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/shared/window.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/activated-route.mock.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/api-translator-pre-post.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/broken-applications.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/configuration.mock.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/credit-application-dao.mock.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/loan-table.mock.service.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-api-credit-application.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-api-loan-container.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-individual.mock.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/vehicle-credit-application.mock.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/.gitkeep create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/LM_Full-Logo_Main.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/cc-images/LCU_cashback.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/cc-images/LCU_rewards.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/cc-images/LCU_secured.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/cc-images/LCU_student.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/checkbox/check.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/eqo.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/files/IMPORTANT TERMS OF OUR HOME EQUITY LINE OF CREDIT.pdf create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/files/What You Should Know About Home Equity Lines of Credit Revised 8-2022.pdf create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/i18n/en.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/loading.gif create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/ncua.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/settings/default_settings.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/applicant-icon-blue-01.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/applicant-icon-gray-01.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/collateral-icon-blue.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/collateral-icon-gray.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/completed-check-icon.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/finish-icon-gray.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/review-icon-blue.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/review-icon-gray.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/browserslist create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/environments/enivronment-common.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/environments/environment.dev.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/environments/environment.e2e.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/environments/environment.local.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/environments/environment.prod.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/environments/environment.qa.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/environments/environment.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/environments/environment.uat.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/favicon.ico create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/index.html create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/karma.conf.js create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/main.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/polyfills.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/styles.scss create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/test.ts create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/tsconfig.app.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/tsconfig.spec.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/tslint.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/src/web.config create mode 100644 LCU.WebApps.External.OnlineLoanApp/tsconfig.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/tslint.json create mode 100644 LCU.WebApps.External.OnlineLoanApp/version.js create mode 100644 LCU.WebApps.External.OnlineLoanApp/wiki/.order create mode 100644 LCU.WebApps.External.OnlineLoanApp/wiki/API-to-Storage-to-Blob-to-ACTion.md create mode 100644 LCU.WebApps.External.OnlineLoanApp/wiki/File-Upload-Component.md create mode 100644 LCU.WebApps.External.OnlineLoanApp/wiki/file-upload-action.png create mode 100644 LCU.WebApps.External.OnlineLoanApp/wiki/file-upload-student.png diff --git a/LCU.WebApps.External.OnlineLoanApp/.editorconfig b/LCU.WebApps.External.OnlineLoanApp/.editorconfig new file mode 100644 index 0000000..a61d1fd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/.editorconfig @@ -0,0 +1,37 @@ +# This file doesn't account for some differences: +# 1.VS Code VS Webstorm spaces inside imports. This can be fixed on the Webstorm side with: +# settings -> editor -> code style -> typescript -> spaces -> within -> ES6 import/export braces +# 2. Webstorm places a carriage return on {}, even for simply functions, adjust with: +# settings -> editor -> code style -> typescript -> wrapping and spacing -> simple blocks in one line +# settings -> editor -> code style -> typescript -> wrapping and spacing -> simple methods in one line + +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false + +# EditorConfig is awesome: https://EditorConfig.org + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,py,ts}] +charset = utf-8 + +# Indentation override for all JS under lib directory +[lib/**.js] +indent_style = space +indent_size = 2 + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/LCU.WebApps.External.OnlineLoanApp/.gitignore b/LCU.WebApps.External.OnlineLoanApp/.gitignore new file mode 100644 index 0000000..284150e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/.gitignore @@ -0,0 +1,412 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd +======= +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +/test-results +/debug.log + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db + +# Patches +*.patch + +# the version file shouldn't be saved +src/environments/version.ts +/.vs/slnx.sqlite +/.vs diff --git a/LCU.WebApps.External.OnlineLoanApp/CHANGELOG.md b/LCU.WebApps.External.OnlineLoanApp/CHANGELOG.md new file mode 100644 index 0000000..ec4e9f3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/CHANGELOG.md @@ -0,0 +1,838 @@ +# Change Log + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + + +# [4.11.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.10.0...v4.11.0) (2021-04-13) + + +### Bug Fixes + +* **CreditApplicationService:** Fix note concatenation ([55dc603](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/55dc603)) + + +### Features + +* **HelocRateModPhaseTwo:** Hide additional fields on initial and collateral pages ([424067b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/424067b)) + + + + +# [4.10.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.9.0...v4.10.0) (2021-03-02) + + +### Bug Fixes + +* **HelocRateMod:** Re hide fields that were left behind in change to hidden logic ([c09ec5b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c09ec5b)) + + +### Features + +* **LoanType:** Add HELOCRateMod Loan Type ([1c264f4](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1c264f4)) + + + + +## [4.9.0](https://landmarkcu.visualstudio.com///compare/v4.8.0...v4.9.0) (2020-11-16) + + +### Features + +* **employment-info:** Add BSA message to income page ([088c0a2](https://landmarkcu.visualstudio.com///commit/088c0a2831848a17b8b5c4efd0fe1bc9e8df70eb)) + + +### Bug Fixes + +* **employment-info:** Move BSA message to translate ([55eb2b1](https://landmarkcu.visualstudio.com///commit/55eb2b14fb49ba98435cafa0f72a2971cddb6ee9)) + + +## [4.8.1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.8.0...v4.8.1) (2020-10-08) + +### Enhancements + +* **HmdaUI:** Replace DoesNotWishToFurnish option with NotProvided ([914ad21b]((https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/914ad21b))) +* **Hmda:** Update Behaviour for Hmda Form. Only send "NotProvided" if it is the only option selected. (Api) + + + +# [4.8.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.3.1...v4.8.0) (2020-08-14) + +### Features + +* **collateralInfoPage:** Add validators to require Wisconsin for HELOC fixed and flex Collaterals ([9719280](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/9719280)) +* **equityMemberInfoPage:** Update Equity Member Info Page Getting Started Message For HELOC Fixed a ([0c2ac8e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/0c2ac8e)) + + +# [4.7.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.3.1...v4.7.0) (2020-06-19) + +### Bug Fixes +* **BranchNameMap:** Pull BranchNameMap from CRIF based on environment ([1373805](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1373805)) +* **Options:** Update Select-Options to include special Loan Types ([248970e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/248970e)) + +### Features + +* **LoanPurposeOptions:** add 'Refinance Cash-Out' to LoanPurposeOptions for Home Equity and HELOC loans ([adbaaec](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/adbaaec)) + + +# [4.6.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.3.1...v4.6.0) (2020-06-12) + + +### Bug Fixes + +* **Covid19:** Removed duplicate text from covid loan ([94b5037](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/94b5037)) + +### Features + +* **BranchOptions:** Add 'Brookfield West' 'Greenfield' update 'Brookfield' to 'Brookfield East' ([682564d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/682564d)) +* **Covid19:** Update to the topmost message for covid 19 loans ([723be00](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/723be00)) + + +# [4.5.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.3.1...v4.5.0) (2020-04-15) + + +### Bug Fixes + +* **SpecialLoan:** Standardize client message field name ([16ea351](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/16ea351)) + + +### Features + +* **COVID-19:** Add COVID-19 loan type ([eb466db](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/eb466db)) +* **Covid19:** ui updates for covid 19 loan enhancements ([5183fb6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/5183fb6)) + + + + +# [4.4.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.3.1...v4.4.0) (2020-03-31) + + +### Features + +* **COVID-19:** Add COVID-19 loan type ([eb466db](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/eb466db)) + + + + +## [4.3.1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.3.0...v4.3.1) (2020-03-16) + + +### Bug Fixes + +* **Schumer:** update, start refactor ([45aa896](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/45aa896)) + + + + +# [4.3.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.2.1...v4.3.0) (2020-01-10) + + +### Bug Fixes + +* **Test:** Fix broken unit test ([1880541](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1880541)) + + +### Features + +* **Logging:** Log to on-premise seq via proxy ([9b768a5](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/9b768a5)) + + + + +## [4.2.2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.2.1...v4.2.2) (2019-11-26) + +### Features + +* **RefiExpress:** Add 8 year term to mortgage refi express loan. + + + +## [4.2.1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v4.2.0...v4.2.1) (2019-10-25) + + +### Bug Fixes + +* **Environment:** Update prod environment flags ([4a466ab](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/4a466ab)) + + + + +# [4.2.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v4.2.0) (2019-10-14) + + +### Bug Fixes + +* **CoreTranslate:** Parse core/apex zipcode into zipcode and extension ([915a0cf](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/915a0cf)) +* **SchumerBoxes:** Update erroneous schumer box text ([a4f4bc7](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/a4f4bc7)) + + + + +## 3.6.1 (2019-07-12) + + +### Bug Fixes + +* **AddressValidation:** Improve css to support variable screen widths ([23dc937](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/23dc937)), closes [#877](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/877) +* **AddressValidation:** Wrap addrss validation headers ([c6bc17a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c6bc17a)) +* **Admin:** Fixed unit tests for snackbar ([96f2d68](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/96f2d68)), closes [#871](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/871) +* **AdminExport:** Add member id to admin export ([1fbf058](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1fbf058)), closes [#1048](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1048) +* **AdminExport:** Add memberid to export ([bf36aad](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/bf36aad)) +* **AppResume:** Do not pre-fill member information when resuming a loan ([766bf4b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/766bf4b)), closes [#1388](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1388) +* **AppStatus:** Add card body to status to support ie11 ([ada0c8a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ada0c8a)), closes [#1248](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1248) +* **AppStatus:** Modify the system config page to better reflect the app status ([94634e1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/94634e1)), closes [#1235](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1235) +* **AppWorkflow:** Allow users to begin existing loans and then switch into a new loan ([cde14b2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/cde14b2)), closes [#1029](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1029) +* **BranchMapping:** Check for null branch mapping when finding channel Id ([f1fa734](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/f1fa734)), closes [#1053](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1053) +* **CookieTests:** Stop mocking cookies for normal tests. Use spys. ([09e58d5](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/09e58d5)), closes [#1095](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1095) +* **DecisionPage:** Increase the time the application will wait for a decision to 40 seconds. ([02798e0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/02798e0)), closes [#971](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/971) +* **Employers:** Set all employers as current moving forward ([dd843b8](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/dd843b8)), closes [#1149](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1149) +* **Environment:** Lock test unless users have test role in AD ([8e56bde](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/8e56bde)) +* **ErrorDisplay:** Have error display component wait for translations to be ready. ([27941af](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/27941af)) +* **ErrorHandling:** Handle errors for member verificaiton and loading existing loans ([b178afb](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b178afb)), closes [#1044](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1044) +* **ErrorReporting:** USe a simplifier to try and relive the circular object references. ([46355c9](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/46355c9)) +* **LoanTranslator:** Use correct ID when translating a loan. ([240055f](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/240055f)) +* **LoginFlow:** Add ability to add verification and core loading per environment ([ed1da05](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ed1da05)) +* **MemberLogin:** App startup was attempting to load existing member applications ([b8e1ae5](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b8e1ae5)), closes [#876](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/876) [#882](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/882) +* **MemberLogin:** Remove return to existing loan link for 3.0 ([63dbb8b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/63dbb8b)) +* **MemberVerification:** Allow member to continue when they can't verify ([c2c7e7e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c2c7e7e)), closes [#1393](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1393) +* **MemberVerification:** Dont show the verification page when a user is an authenticated non member ([56f738b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/56f738b)), closes [#1090](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1090) +* **MockApiModel:** Fix model to correctly populate loan application ([7322112](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7322112)), closes [#1128](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1128) +* **PersonalInfo:** Reset co-applicant has-same-address flag on co-applicant status change ([22bb115](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/22bb115)), closes [#767](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/767) [#709](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/709) +* **Processing:** Report all processing discrepencies to bugsnag for debugging ([f0e6138](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/f0e6138)) +* **ProcessingPage:** Update unit tests, expand wait time ([f9f59fa](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/f9f59fa)) +* **SpouseDisclosure:** Show disclosure for coapplicant spouse ([5563bf4](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/5563bf4)), closes [#1050](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1050) +* **Submit:** Use loan id directly from adjusted web service ([0c728c6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/0c728c6)) +* **SummaryComments:** Fix allowance of special characters, prevent bad form submissions ([fe19e39](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/fe19e39)), closes [#1172](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1172) +* **TattleSpouse:** Update tattle spouse copy to be in orange box, move to bottom. ([23e2dcf](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/23e2dcf)), closes [#890](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/890) +* **Tests:** Fix broken unit test ([e8afd47](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e8afd47)) +* **Timeout:** Change timeout info text depending on the preceding page ([c94e68f](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c94e68f)), closes [#880](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/880) +* **Timeout:** Forward user to landmarkcu.com when timing out completed application ([c924ed5](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c924ed5)), closes [#967](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/967) +* **TimeOutComponent:** Dont reset credit application when one doesnt exist ([5e59d24](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/5e59d24)), closes [#1176](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1176) + + +### Features + +* **Admin:** Refactor admin to support better error handling ([ce5d790](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ce5d790)), closes [#871](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/871) +* **Admin:** UI for customer redirect when app is turned off. UI for admins to turn app off. ([870b0c6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/870b0c6)), closes [#1142](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1142) +* **AdminSystem:** Display server errors in snack bar prompt ([db3ce19](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/db3ce19)), closes [#871](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/871) +* **AppLoader:** Display loan icon next to existing loan list ([3c0c305](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/3c0c305)), closes [#1125](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1125) +* **AppStatus:** Added a new decision status for tracking in admin ([efbb683](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/efbb683)), closes [#1133](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1133) +* **BoatTerms:** Update minimum term length for boats to 63 months ([d3cfcec](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d3cfcec)), closes [#883](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/883) +* **CookieDisclosure:** Additional text for cookie disclosure ([bd4f297](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/bd4f297)), closes [#963](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/963) +* **CoreAccess:** Change to all cookie based security, no direct ID manipulation ([607487c](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/607487c)) +* **CoreAccess:** Prefill member info, get individual id ([091adae](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/091adae)), closes [#799](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/799) +* **CoreIntegration:** Replace mock services with real restful calls ([c40d42e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c40d42e)), closes [#1162](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1162) +* **EmploymentStatus:** Removed retired status from dropdown ([62f3159](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/62f3159)), closes [#1180](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1180) +* **ErrorHandling:** Report errors to logging service instead of bugsnag ([785400c](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/785400c)), closes [#1179](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1179) +* **MemberLogin:** Catch login status and throw appropriate error messages ([d002541](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d002541)), closes [#1129](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1129) +* **MemberLogin:** Disable member login for 3.0 release ([758ca0b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/758ca0b)), closes [#931](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/931) +* **MemberLogin:** Route user to lmcu login when they click the existing app link ([6b28315](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/6b28315)), closes [#1037](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1037) +* **MemberVerification:** Front end implementation of core work. Includes verification & continue of ([fc57b12](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/fc57b12)), closes [#766](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/766) +* **Options:** Added test coverage to option classes ([636eb8b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/636eb8b)), closes [#1218](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1218) +* **Prefill:** Prefill member primary address ([7f2c57d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7f2c57d)) +* **ReviewWording:** Allow review wording to be based on loan type ([ff2fb4a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ff2fb4a)) +* **StatusToolTip:** Added tooltip to status column in admin page ([62058dd](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/62058dd)), closes [#1163](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1163) + + + + +# [4.1.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v4.1.0) (2019-09-04) + +### Bug Fixes + +* **CoreTranslate:** Parse core/apex zipcode into zipcode and extension ([915a0cf](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/915a0cf)) + + +# [4.0.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v4.0.0) (2019-07-26) + + +### Features + +* **AppWorkflow:** Allow users to begin existing loans and then switch into a new loan ([cde14b2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/cde14b2)), closes [#1029](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1029) +* **LoanTranslator:** Use correct ID when translating a loan. ([240055f](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/240055f)) +* **LoginFlow:** Add ability to add verification and core loading per environment ([ed1da05](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ed1da05)) +* **MemberLogin:** App startup was attempting to load existing member applications ([b8e1ae5](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b8e1ae5)), closes [#876](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/876) [#882](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/882) +* **MemberLogin:** Remove return to existing loan link for 3.0 ([63dbb8b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/63dbb8b)) +* **MemberVerification:** Allow member to continue when they can't verify ([c2c7e7e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c2c7e7e)), closes [#1393](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1393) +* **MemberVerification:** Dont show the verification page when a user is an authenticated non member ([56f738b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/56f738b)), closes [#1090](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1090) +* **Admin:** Refactor admin to support better error handling ([ce5d790](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ce5d790)), closes [#871](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/871) +* **Admin:** UI for customer redirect when app is turned off. UI for admins to turn app off. ([870b0c6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/870b0c6)), closes [#1142](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1142) +* **AdminSystem:** Display server errors in snack bar prompt ([db3ce19](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/db3ce19)), closes [#871](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/871) +* **AppLoader:** Display loan icon next to existing loan list ([3c0c305](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/3c0c305)), closes [#1125](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1125) +* **AppStatus:** Added a new decision status for tracking in admin ([efbb683](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/efbb683)), closes [#1133](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1133) +* **BoatTerms:** Update minimum term length for boats to 63 months ([d3cfcec](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d3cfcec)), closes [#883](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/883) +* **CookieDisclosure:** Additional text for cookie disclosure ([bd4f297](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/bd4f297)), closes [#963](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/963) +* **CoreAccess:** Change to all cookie based security, no direct ID manipulation ([607487c](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/607487c)) +* **CoreAccess:** Prefill member info, get individual id ([091adae](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/091adae)), closes [#799](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/799) +* **CoreIntegration:** Replace mock services with real restful calls ([c40d42e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c40d42e)), closes [#1162](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1162) +* **EmploymentStatus:** Removed retired status from dropdown ([62f3159](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/62f3159)), closes [#1180](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1180) +* **ErrorHandling:** Report errors to logging service instead of bugsnag ([785400c](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/785400c)), closes [#1179](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1179) +* **MemberLogin:** Route user to lmcu login when they click the existing app link ([6b28315](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/6b28315)), closes [#1037](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1037) +* **MemberVerification:** Front end implementation of core work. Includes verification & continue of ([fc57b12](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/fc57b12)), closes [#766](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/766) +* **Options:** Added test coverage to option classes ([636eb8b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/636eb8b)), closes [#1218](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1218) +* **Prefill:** Prefill member primary address ([7f2c57d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7f2c57d)) +* **ReviewWording:** Allow review wording to be based on loan type ([ff2fb4a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ff2fb4a)) +* **StatusToolTip:** Added tooltip to status column in admin page ([62058dd](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/62058dd)), closes [#1163](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1163) + + +# [3.6.1](2019-07-12) + +### Other + +* **Processing:** Display different messages for for credit cards. +* **SchumerBoxes:** Update text of schumer boxes + + +# [3.6.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.5.1...v3.6.0) (2019-04-29) + + +### Bug Fixes + +* **Admin:** Fixed unit tests for snackbar ([96f2d68](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/96f2d68)), closes [#871](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/871) +* **Employers:** Set all employers as current moving forward ([dd843b8](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/dd843b8)), closes [#1149](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1149) +* **Environment:** Lock test unless users have test role in AD ([8e56bde](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/8e56bde)), no ticket +* **ErrorHandling:** Handle errors for member verification and loading existing loans ([b178afb](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b178afb)), closes [#1044](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1044) +* **MemberVerification:** Dont show the verification page when a user is an authenticated non member ([56f738b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/56f738b)), closes [#1090](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1090) +* **Submit:** Use loan id directly from adjusted web service ([0c728c6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/0c728c6)), no ticket +* **SummaryComments:** Fix allowance of special characters, prevent bad form submissions ([fe19e39](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/fe19e39)), closes [#1172](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1172) +* **TimeOutComponent:** Dont reset credit application when one doesnt exist ([5e59d24](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/5e59d24)), closes [#1176](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1176) +* **USPS:** Better handle USPS errors when users enter nonsense addresses, closes 1157 + +### Features + +* **Admin:** Refactor admin to support better error handling ([ce5d790](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ce5d790)), closes [#871](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/871) +* **Admin:** UI for customer redirect when app is turned off. UI for admins to turn app off. ([870b0c6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/870b0c6)), closes [#1142](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1142) +* **AdminSystem:** Display server errors in snack bar prompt ([db3ce19](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/db3ce19)), closes [#871](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/871) +* **AppLoader:** Display loan icon next to existing loan list ([3c0c305](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/3c0c305)), closes [#1125](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1125) +* **AppStatus:** Added a new decision status for tracking in admin ([efbb683](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/efbb683)), closes [#1133](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1133) +* **EmploymentStatus:** Removed retired status from dropdown ([62f3159](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/62f3159)), closes [#1180](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1180) +* **MemberLogin:** Catch login status and throw appropriate error messages ([d002541](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d002541)), closes [#1129](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1129) +* **MemberLogin:** Route user to lmcu login when they click the existing app link ([6b28315](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/6b28315)), closes [#1037](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1037) +* **Options:** Added test coverage to option classes ([636eb8b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/636eb8b)), closes [#1218](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1218) +* **StatusToolTip:** Added tooltip to status column in admin page ([62058dd](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/62058dd)), closes [#1163](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1163) +* **ApiRoot:** Further change API root to redirect to client, fixing both Azure and lost consumers, closes #1156 + +### Other + +* **MemberLogin:** Disable member login, pre-fill and other 4.0 code. + + +# [3.5.0-Release](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v3.5.0-Release) (2019-04-10) + + +### Bug Fixes + +* **AdminExport:** Add memberid to export ([bf36aad](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/bf36aad)) + + +### Features + +* **MemberVerification:** Front end implementation of core work. Includes verification & continue of ([fc57b12](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/fc57b12)), closes [#766](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/766) + + + + +# [3.5.1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.5.0...v3.5.1) (2019-04-15) + + +### Bug Fixes + +* **LoanAdmin:** All users to submit loans marked VENDOR_ERROR to CRIF, closes #1131 + + +# [3.5.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v3.5.0) (2019-04-10) + + +### Bug Fixes + +* **AddressValidation:** Wrap addrss validation headers ([c6bc17a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c6bc17a)), closes #1067 +* **AdminExport:** Add member id to admin export ([1fbf058](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1fbf058)), closes [#1048](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1048) +* **BranchMapping:** Check for null branch mapping when finding channel Id ([f1fa734](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/f1fa734)), closes [#1053](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1053) +* **PersonalInfo:** Reset co-applicant has-same-address flag on co-applicant status change ([22bb115](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/22bb115)), closes [#767](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/767) [#709](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/709), #993 +* **SpouseDisclosure:** Show disclosure for coapplicant spouse ([5563bf4](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/5563bf4)), closes [#1050](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1050) +* **TattleSpouse:** Update tattle spouse copy to be in orange box, move to bottom. ([23e2dcf](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/23e2dcf)), closes [#890](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/890) + +### Features + +* **LoanStorage:** Cache mongo client instance, add more tests., closes #767, #993 +* **Azure:** move the api status call to root so azure sees it, closes #752 + + +# [3.4.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v3.4.0) (2019-04-03) + + +### Bug Fixes + +* **ProcessingPage:** Update unit tests, expand wait time ([f9f59fa](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/f9f59fa)) +* **Timeout:** Forward user to landmarkcu.com when timing out completed application ([c924ed5](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c924ed5)), closes [#967](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/967) +* **BranchMapping:** Handle double submission errors in IE 11, closes #992 +* **CRIF:** Handle Error 13 as a 'try again' error, closes #710 + +### Features + +* **CookieDisclosure:** Additional text for cookie disclosure ([bd4f297](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/bd4f297)), closes [#963](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/963) +* **ApiStatus:** Report API status as root route for Azure, closes #752 +* **MongoDB:** Cache Cosmos/Mongo connection for improved performance + + +# [3.3.2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v3.4.0) (2019-03-28) + + +### Bug Fixes +* **Processing:** Report all processing discrepencies to bugsnag for debugging ([f0e6138](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/f0e6138)) + + + +# [3.3.1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v3.4.0) (2019-03-27) + + +### Bug Fixes + +* **DecisionPage:** Increase the time the application will wait for a decision to 40 seconds. ([02798e0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/02798e0)), closes [#971](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/971) + + +# [3.3.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v3.3.0) (2019-03-21) + + +### Bug Fixes + +* **MemberLogin:** Remove return to existing loan link for 3.0 ([63dbb8b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/63dbb8b)) +* **TattleSpouse:** Update tattle spouse copy to be in orange box, move to bottom. ([23e2dcf](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/23e2dcf)), closes [#890](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/890) + +### Features + +* **MemberLogin:** Disable member login for 3.0 release ([758ca0b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/758ca0b)), closes [#931](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/931) +* **BoatTerms:** Update minimum term length for boats to 63 months ([d3cfcec](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d3cfcec)), closes [#883](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/883) + + +## [3.2.1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.2.0...v3.2.1) (2019-03-20) + + +### Bug Fixes + +* **AddressValidation:** Improve css to support variable screen widths ([23dc937](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/23dc937)), closes [#877](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/877) +* **MemberLogin:** App startup was attempting to load existing member applications ([b8e1ae5](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b8e1ae5)), closes [#876](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/876) [#882](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/882) +* **Timeout:** Change timeout info text depending on the preceding page ([c94e68f](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c94e68f)), closes [#880](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/880) + + + + +# [3.2.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.0.0...v3.2.0) (2019-03-20) + + +### Bug Fixes + +* **BranchConfig:** Last updated date was not shown on branch admin screen. ([17298e1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/17298e1)), closes [#833](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/833) +* **BranchExport:** Remove quotes from branch export, add more testing. ([e74d8af](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e74d8af)), closes [#872](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/872) +* **LoanAPI:** Remove forced exception test, allowing loans to complete. closes #873, backend. +* **MemberLogin:** App startup was attempting to load existing member, closes #883, addresses #876 + +### Features + +* **Admin:** Add error with start date and end date format ([1a25207](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1a25207)), closes [#878](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/878) +* **TattleSpouse:** Add additional tattle spouse text inside orange box ([8010fb8](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/8010fb8)), closes [#874](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/874) + + + + +# [3.1.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v3.0.0...v3.1.0) (2019-03-19) + + +### Bug Fixes + +* **BranchConfig:** Last updated date was not shown on branch admin screen. ([17298e1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/17298e1)), closes [#833](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/833) + + + +# [3.0.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.10.0...v3.0.0) (2019-03-18) + + +### Bug Fixes + +* **Admin:** Save filter settings between pages ([ad8efc5](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ad8efc5)), closes[#1412] +* **Admin:** Sort fields as case insensitive ([72ac98e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/72ac98e)), closes[#1411] +* **AdminDropDownLists:** Fixed lists in IE ([dde4448](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/dde4448)), closes[#1396] +* **Alimony:** Pass alimony information correctly to wizard service, fix tests. ([8721e6c](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/8721e6c)), no ticket +* **AppLoader:** Fix crash on loading of empty list of existing loans ([a170c65](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/170c65)), no ticket +* **AppRouting:** Use ternary operators instead of functions for canactivate ([ec5852c](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ec5852c)), closes[#1465] +* **BranchMapping:** Map 53037 to westbend branch ([e3de1fe](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e3de1fe)), closes[#1407] +* **CoApplicantSpouse:** Fixes a broken unit test ([ca68c74](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ca68c74)), no ticket +* **CollateralInfoPage:** Patch occupancy type using enum instead of string ([c2fe840](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c2fe840)), closes[#1196] +* **DisclosureDate:** Dont initialize valuealize value ([fb5a9f6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/fb5a9f6)), closes[#1406] +* **Security:** Send withcredentials in more requests ([10c5674](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/10c5674)), no ticket +* **HeaderStatus:** Remove loantype from header when navigating back to select loan page ([de709e7](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/de709e7)), closes[#1362] +* **LandingPage:** Changed language on vehicle selection and headers ([8a2bacd](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/8a2bacd)), closes[#1363] +* **NameSuffix:** Deleted an important line of code in the last merge ([060021f](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/060021f)), no ticket +* **PersonalInfo:** Add correct icons to Middle initial and suffix display ([b496880](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b496880)), closes[#1341] +* **Processing:** Rewriting processing page to avoid out of order errors ([be1bf7f](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/be1bf7f)), closes [#1365](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1365) +* **ProcessingPage:** Capital S on last line of status ([a7087af](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/a7087af)), closes[#1340] +* **RouteGuards:** Activate route guards ([59e9416](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/59e9416)), closes[1332] +* **SchumerBoxes:** Update Schumer Box rates for reward, student and cash back ([d9fc111](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d9fc111)), no ticket +* **Security:** Only all users with admin role to see admin screen, test role for qa access. ([183073b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/183073b)), closes [#1329](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1329) +* **Security:** Request from microsoft graph if specific user is in admin group ([260922a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/260922a)), closes [#1329](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1329) +* **Summary:** Format Other Income amount as dollars ([9190f6c](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/9190f6c)), closes [#1335](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1335) +* **SummaryCollateral:** Fix collateral typo ([e508cb4](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e508cb4)), closes[#1187] +* **SummaryPageButton:** Change disabled color to grey ([c5e4e4f](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c5e4e4f)), closes[#1388] +* **UrlParsing:** Strip off extra characters when parsing urls ([1c057bd](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1c057bd)), closes [#1323](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1323) +* **VehicleTranslator:** Use credit app purpose of loan for boats, rvs, powersports ([d4f12f7](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d4f12f7)), closes[#1401] +* **AmountValidation:** Added amount validation on monthly payment and vehicle collateral ([739f0eb](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/739f0eb)), closes #1342 + +### Features + +* **AddressValidation:** Increased header line height to support all browsers ([4e48711](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/4e48711)), closes #1370 +* **AddressVerification:** Move button location for better ux ([2c7adba](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/2c7adba)), closes #1370 +* **Admin:** New admin landing page for choosing features ([4e27e73](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/4e27e73)), closes #1065 +* **AdminExport:** Updated csv export with additional fields ([19899c8](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/19899c8)), closes #1419 +* **AdminHome:** Added home navigation for admin pages ([15fa86b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/15fa86b)), closes #1449 +* **AdminPage:** Allow user input of date field ([eb79236](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/eb79236)), closes #1416 +* **AdminSystem:** Export table into csv ([fc75b4e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/fc75b4e)), closes #1231 +* **AdminSystem:** Save vendor loan id as part of the json model. Display in admin system. ([eb5d0d2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/eb5d0d2)), closes #1170 +* **AdminSystem:** Tooltip for search field ([022d998](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/022d998)), closes #1414 +* **AdminTimeout:** Redirect users to admin home when timed out ([16d7418](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/16d7418)), closes #1418 +* **API:** Update api urls to custom domain names ([3a53dc6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/3a53dc6)), closes [#1402](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1402) [#1378](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1378) +* **AppNote:** Capture datetime of when user completes disclosure page ([10cddd7](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/10cddd7)), closes #1406 +* **BranchMapping:** Allow download and upload of branch mappings with error checking ([d5581db](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d5581db)), closes [#1443](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1443) +* **CookieDisclosure:** Added cookie disclosure to first page of application ([a2aac4d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/a2aac4d)), closes #1404 +* **CopyRightYear:** Use year function to get current year for copyright ([39872c2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/39872c2)), closes #1346 +* **Disclosure:** Change text of disclosure box ([594e307](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/594e307)), closes #1417 +* **e2eTests:** Added e2e test coverage to new landing pages ([4c29727](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/4c29727)), no ticket. +* **Income:** Allow multiple other and employer incomes. ([dc7947d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/dc7947d)), closes [#565](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/565), closes #1181 +* **LoanTerms:** Updated vehicle loan terms ([84b3776](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/84b3776)), closes #1397 +* **MemberLogin:** Cookie auth, saml end point, UX for members ([609bc7e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/609bc7e)), closes #1379 +* **MultipleEmployers:** Added ability to add up to three employment incomes ([4be1d4b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/4be1d4b)), closes [#1181](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1181) +* **NameHeaders:** Format applicant name as title case when shown back to the user ([5491f19](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/5491f19)), closes #1427 +* **Notes:** Force user to select primary contact method ([27934e6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/27934e6)), closes #1305 +* **PageTitles:** Change page titles to sentence style ([b1326ea](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b1326ea)), closes #1428 +* **PartialAppLoader:** Allows saved applications to be loaded and continued seemlessly ([39f446d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/39f446d)), closes [#1198](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1198) +* **PromoCode:** Add promo code field to the summary page ([a9caadf](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/a9caadf)), closes #1334 +* **SalaryAmount:** Changed minimum salary amount to 0 dollars ([cfed9d2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/cfed9d2)), closes #1343 +* **SaveAndContinue:** Adjusts API calls to match new save => update => submit flow ([20098a7](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/20098a7)), closes [#1368](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1368) +* **Security:** Limit access to admin pages to specific AD group, limit DEV/QA to both groups. ([e981887](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e981887)), closes #1325 +* **SuffixSelect:** Add choose one select to suffix select ([dc05c6a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/dc05c6a)), closes #1422, #1341 +* **TimeoutPage:** Timeout page will now route back to admin if user was at admin ([796e48e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/796e48e)), closes #1451 +* **Translator:** Append timestamp to en.json file to prevent caching ([daeb36e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/daeb36e)), closes #1424 +* **Translator:** Convert error messages to translator. ([922a64c](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/922a64c)), closes #1290 +* **VisaTrademark:** Added registered trademark to visa text ([e57bd91](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e57bd91)), closes #1389 + + +### Performance Improvements + +* **AdminModule:** Create unique admin module for the admin system ([a261206](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/a261206)), no ticket +* **MemberComponent:** Added unit tests to the member components and services ([d1bd1f9](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d1bd1f9)), no ticket + + +### Reverts + +* **CosmosIdInSession:** Roll back previous changes ([8862ca0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/8862ca0)), closes [#1372](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1372) + + +# [2.1.1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.10.0...v2.1.2) (2019-02-13) + + +### Bug Fixes + +* **PersonalInfoForm:** Add default value to suffix ([06d8cf7](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/06d8cf7)), closes [#1422](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1422) +* **Admin:** Update list of zip codes and branch mappings + + +# [2.1.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.10.0...v2.1.0) (2019-02-11) + + +### Bug Fixes + +* **SchumerBoxes:** Update Schumer Box rates for reward, student and cash back ([d9fc111](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d9fc111)) + + +### Features + +* **Environment:** Up API urls in environment files to new domain names ([6385803](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/6385803)), closes [#1402](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1402) + + +# [2.0.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.10.0...v2.0.0) (2019-01-11) + + +### Bug Fixes + +* **CollateralPage:** Adds a max value validator to the "Estimated Value" field ([e32ae31](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e32ae31)), closes [#1173](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1173) + +### Features + +* **LoanTerms:** Updated 2019 loan terms for vehicles ([87a7a50](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/87a7a50)) + + +# [1.9.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.8.2...v1.9.0) (2019-01-07) + + +### Bug Fixes + +* **AddressValidationView:** updates the UX for Address Validation piece ([edc581a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/edc581a)), closes [#1242](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1242) +* **LoanPurpose:** Correct display the default loan purpose selection for heloc loans ([e94a646](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e94a646)), closes [#1324](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1324) +* **MemberInfoComponent:** Display error message with matching field label ([c87c1c8](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c87c1c8)) +* **NumbersWithDecimals:** Numbers with decimals not 'unmasked' correctly ([ed163c6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ed163c6)), closes [#1240](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1240) + + +### Features + +* **AuthenticationGuard:** Configures The Auth guard to authenticate non-prod environments ([d4e0705](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d4e0705)), closes [#1217](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1217) +* **CurrencyFormatter:** Eliminate decimals when not needed ([41bee05](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/41bee05)) +* **EmploymentInfoPage:** Defaults the number of months employed to "0" ([c422bd4](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c422bd4)), closes [#1195](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1195) +* **PersonalInfo:** New suffix field on personal info pages ([d8e6c96](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d8e6c96)) + + +## [1.8.2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.8.1...v1.8.2) (2018-12-12) + +### Bug Fixes + +* **LienPayoffFont:** Updates the font for "Payoff" prompt on CollateralPage. ([7c2a27b8]) +(https://landmarkcu.visualstudio.com/_git/LoanWizard/commit/7c2a27b8d4c2bb28460682092da3e2532475f003), closes [#1194] + + + +## [1.8.1](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.8.0...v1.8.1) (2018-12-10) + +### Bug Fixes + +* **VehicleValutation:** Changes the messaging for vehicle valuation. ([826dcca1]), closes [#1227] + + +# [1.8.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.7.0...v1.8.0) (2018-12-04) + + +### Bug Fixes + +* **Headers:** Remove feature header, causing browser problems. ([b97c278](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b97c278)) +* **SummaryPage:** Full info spouse sections of the summary page did not have a title ("Your spouse's ([5ef5e58](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/5ef5e58)), closes [#1205](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1205) +* **SaveOnlyValidIndividuals:** The backend will validate Individual's and reject the save if they d ([af01465](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/af01465)), closes [#1202](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1202) +* **CORS:** Move CORS from Azure to Code, no ticket. + +### Features + +* **ShowSavingProgress:** Upon saving, the UI will disable the "NEXT" button and add a 'inprogress' ([8484bda](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/8484bda)), closes [#1199](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1199) + + +# [1.7.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.4.0...v1.7.0) (2018-12-03) + + +### Bug Fixes + +* **AddressValidation:** Hides the compare address view when address group is invalid ([16fbb07](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/16fbb07)), closes [#1165](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1165) +* **EquityTranslator:** Allow null values in equity/vehicle translator ([b4c44db](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b4c44db)), no ticket. +* **FontAndAddress:** Fixed a font class. Fixed 'saving' real estate verified address ([7d16084](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7d16084)), closes [#1194](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1194) +* **Header:** Don't link header to main site, update vehicle loan type header ([d3379fd](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d3379fd)), closes [#1131](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1131) [#1132](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1132) +* **I18N:** Fix error messages to allow custom error messages ([c6f73ad](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c6f73ad)), closes #1149 +* **LoanTermYearCalc:** Grab present year loan terms for future years ([0155a93](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/0155a93)), closes #1096 +* **PenTest:** Update web.config with security headers, remove location.href reference. ([2fd35c9](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/2fd35c9)), closes #1176, #1177 +* **ProcessingPage:** Changed the bugsnag error on decision page timeout to a notification in bugsnag ([d6457d8](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d6457d8)), closes [#1150](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1150) +* **Spouses:** 2 fixes, spouse info needs to disappear when we change to co-applicant spouse, 'same r ([9f7cf7d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/9f7cf7d)), closes [#1141](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1141) +* **VehicleSummaryTemplate:** hide vehicle condition for the vehicle types that dont ask for conditio ([1f7b495](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1f7b495)), closes [#1155](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1155), #1172 +* **Waypoints:** fix mobile way point messaging ([7505116](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7505116)), closes [#1127](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1127) +* **Branch-Zip:** Update zip/branch mapping, closes #1174 + + +### Features + +* **AddressValidation:** Adds address validation functionality, injects it to all address input form ([a605680](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/a605680)), closes [#1178](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1178) [#1180](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1180) [#1168](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1168) +* **AddressValidation:** Adds address validation story to UI ([8f743b2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/8f743b2)), closes [#84](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/84) +* **AdminPage:** A new admin page was created for landmark employees to review loans ([0f0e4ea](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/0f0e4ea)), closes #17 +* **bugsnag:** add error to bugsnag for decisioning ([da3a3df](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/da3a3df)), no ticket +* **CommonGroups:** Only alpha characters in the name fields ([1a90415](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1a90415)), closes [#1110](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1110) +* **employment-income:** limits on hourly and annual income ([4165165](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/4165165)), closes #1160 +* **environment:** use before send hook to stop bug snag reports ([d362192](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d362192)), no ticket +* **LienPosition:** Added lien position functionality to the front end ([e309377](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e309377)), closes [#1026](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1026), #1025 +* **LienPositionManufacturedHome:** Added logic on front end for lien position for manufactured home ([3886202](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/3886202)), closes [#1153](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1153) +* **LoanSubmission:** Save progress of loan applications. Submit via cosmos db ([b81fda2](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b81fda2)), closes #17 +* **LoanTerms:** Signature and preapproval support up to and year truncation ([cec6f5b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/cec6f5b)) +* **LoanTerms:** Up to appended on shortest loan term ([7e71e66](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7e71e66)) +* **MemberComponentVehicleComponent:** Dropdowns disabled when no valid options exist, updated e2e t ([183b841](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/183b841)), closes [#1125](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1125) +* **MetaData:** Add loan meta data into loan container ([4fbe780](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/4fbe780)), closes #1170 +* **PersonalInfoForms:** Add custom message for alpha only name fields ([78486ae](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/78486ae)), closes [#1110](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1110) +* **robots:** disable bots from crawling site ([c16fdb0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/c16fdb0)), closes #1152 +* **SECURITY:** Upgrade IIS headers in web.config per pentest ([8912a59](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/8912a59)), closes #1176, #1177 +* **Translate:** Allow over-riding individual error messages, pull messages from Translate file ([000e025](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/000e025)), closes [#1149](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1149) +* **vehicle-collateral:** Vehicle Seller Conditional Show Hide ([ba00d24](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ba00d24)), closes #1082 +* **KeyVault:** Update API to use Azure Key Vault to read API keys and other secrets, closes #1041 + + +### Performance Improvements + +* **bugsnag:** move bug snag api key ([ce0bb7b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/ce0bb7b)), closes #1041 + + + +# 1.6.1(2018-11-12) + + +### Bug Fixes + +* **SchumerBoxes:** Update Schumer Box rates for reward, student and cash back + + + +# [1.6.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.4.0...v1.6.0) (2018-11-12) + + +### Bug Fixes + +* **LoanTermYearCalc:** Grab present year loan terms for future years ([0155a93](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/0155a93)), closes #1147 + +### Features + +* **Translate:** Allow over-riding individual error messages, pull messages from Translate file ([000e025](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/000e025)), closes [#1149](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1149) +* **CRIF:** Equity Loan without HMDA information for non-signing spouse (co-applicant to CRIF), closes #1140 + + + +# [1.5.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.4.0...v1.5.0) (2018-11-08) + + +### Bug Fixes + +* **Header:** Don't link header to main site, update vehicle loan type header ([d3379fd](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d3379fd)), closes [#1131](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1131) [#1132](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1132) +* **Spouses:** Spouse removed when changed to co-applicant spouse ([9f7cf7d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/9f7cf7d)), closes [#1141](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1141) +* **Spouses:** Residence is same was not properly required ([9f7cf7d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/9f7cf7d)), closes [#1141](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1141) +* **Waypoints:** fix mobile way point messaging ([7505116](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7505116)), closes [#1127](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1127) +* **TermValidator:** Support the entire range of vehicle years (0 - 9999) without failing, closes #1135 +* **PowersportLoanTermValidator:** Use estimated amount for loanterm calculations on powersports, closes #1136 +* **UX:** Show inverted correct and error field icons (x/checkmark), closes #1103 + +### Features + +* **CommonGroups:** Only alpha characters in the name fields ([1a90415](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1a90415)), closes [#1110](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1110) +* **LoanTerms:** Signature and preapproval support up to and year truncation ([cec6f5b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/cec6f5b)), closes #1098 +* **LoanTerms:** Up to appended on shortest loan term ([7e71e66](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7e71e66)), closes #1098 +* **PersonalInfoForms:** Add custom message for alpha only name fields ([78486ae](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/78486ae)), closes [#1110](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1110) +* **vehicle-collateral:** Vehicle Seller Conditional Show Hide ([3e1820a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/3e1820a)), closes #1082 + + +# [1.4.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.3.0...v1.4.0) (2018-11-07) [Patch] + +### Bug Fixes +* **Header:** Fix bug where mobile header displays [object object] instead of loan type, closes #1127 +* **CRIF:** Update vehicle seller wording in app note, closes #1128 +* **Setting:** Update minimum amount for boat and manufactured terms in loan term settings, closes #1130 + + +# [1.4.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.3.0...v1.4.0) (2018-11-06) + + +### Bug Fixes + +* **ApiTranslator:** Apply TCPA disclosure individually ([b9da0ec](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b9da0ec)), closes [#1092](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1092) +* **ApiTranslator:** Unmask the other income and child support consistently with income ([da22a7e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/da22a7e)), closes [#1080](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1080) +* **Build:** Fix the build based on injecting a non DI service ([32af4e6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/32af4e6)) +* **SpouseInfoSaving:** Spouse Information was getting overwritten by a 'save' call in AWService ([a59d69e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/a59d69e)), closes [#1102](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1102), #1124 +* **SpouseTypes:** When spouse-related input changes, some previously entered info should be nullifie ([b0a6f8e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b0a6f8e)), closes [#1090](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1090) [#1124](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1124) +* **VehicleCollateralTerms:** When 'termOptions' change in local state we must ensure that the value ([b9f2eba](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b9f2eba)), closes [#1096](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1096), closed #1096, #1097 + + +### Features + +* **Email_format:** NG's default email validator did not match the regex pattern that CRIF requires ([447438e](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/447438e)), closes [#1115](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1115) +* **HMDA_Gender:** Gender doesn't have to be Male or Female, it can be both ([fd4d7a7](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/fd4d7a7)), closes [#1116](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1116) +* **VehicleTerms:** Vehicle terms were refactored and updated accross all vehicle types ([7c2770d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7c2770d)), closes #1096, #1097 +* **CRIF** Vehicle seller added as an additional application note, closes #1082 +* **CRIF** Uppercase user entered fields, closes #1087 +* **CRIF** Send all dates in correct time zone, closes #1109 + + + + +# [1.3.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.2.0...v1.3.0) (2018-11-02) + + +### Bug Fixes + +* **PowerSports:** Update powersports labels, also display correct vehicle type on summary screen. ([80907fc](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/80907fc)), Closes #1099 +* **residencesummary:** Adjust previous address heading on summary page ([3bb0c52](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/3bb0c52)), closes [#1107](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1107) +* **SpouseButtons:** Format spouse buttons to match other buttons ([bade806](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/bade806)), closes [#1104](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1104) +* **spouselabel:** Uppercase all spouse labels used on buttons for page navigation ([b5b3ea9](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/b5b3ea9)), closes [bug#1106](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1106) +* **VehicleCollateral:** "Term of Loan" was not patching correctly. ([1de237d](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1de237d)), closes [#1117](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1117) +* **VehicleCollateral:** "Term of Loan" was not patching correctly. ([115e508](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/115e508)), closes [#1117](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1117) + + +### Features + +* **Labels:** Support showing separate labels for loan type in header and copy ([1f6c279](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1f6c279)), closes #1100 +* **SummaryPage:** Add loan term to summary page ([2778fdd](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/2778fdd)), closes [#1101](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1101) + + + + +# [1.2.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.0.10...v1.2.0) (2018-10-30) + + +### Bug Fixes + +* **ConfigurationService:** Change the service to not load from the server more than once. ([66645db](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/66645db)) +* **decisionpage:** fix loan terms for powersports ([7541bce](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/7541bce)) +* **decisionpage:** use config service for url instead of hardcoded ([aea7278](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/aea7278)) +* **MonthsResidedEmployed:** Fix the maximum months resided and employed to be < 131 per crif max ([1adb412](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1adb412)) +* **vehiclecollateral:** refactor vc component ([e06d3b8](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/e06d3b8)) +* **Labels:** Fix two appliant -> applicant labels. + +### Features + +* **CRIF** Send HMDA loan flag + + + +# [1.1.0](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.0.10...v1.1.0) (2018-10-29) + + +### Bug Fixes + +* **collateralinfo:** use msrp for term length ([1962cda](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/1962cda)) +* **Labels:** Update all powersports labels to read Personal Water Craft ([aab02cc](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/aab02cc)) +* **vehiclecollateral:** use msrp for loan terms ([914f36b](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/914f36b)) +* **TCPA:** Opt-out now sent to CRIF correctly + + +### Features + +* **DisclosureInfo:** Fixes a compile error ([72fd9db](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/72fd9db)), closes [#1085](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1085) +* **ApiTranslator:** Adds disclosure information to loan application submission ([3d6103a](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/3d6103a)), closes [#1085](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1085) + + + + +## 1.0.10 (2018-10-26) + +### Bug Fixes +* **Powersports** Fix the power sports label to be Personal Water Craft / Jet Ski. +* **LoanTypes** Fixes Signature loan apicode and adds unique api translator for headers. +* **DecisionPage** allow all servers besides production to display the loan id. (QA) +* **ApiTranslator** Properly translate secondary phone #. +* **ApiTranslator** Include middle name for tattle spouse. +* **ApiTranslator** Fix HMDA notice disclosed field. +* **CRIF** Fix HMDA determination fields, gender, ethnicity, race, fix not-applicable value. + +### Features +* **CRIF** Alimony/Child support are no longer INCOME +* **LoanLimits** Adjust vehicle loan limits to 250 from 5000 + + +## 1.0.8 (2018-10-24) + + +### Bug Fixes + +* **ApiTranslator:** Correctly send the hmdaNoticeDisclosed field to the server. ([014c185](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/014c185)), closes [#1069](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/issues/1069) +* **Api:** Correctly format the hmda fields for do not disclose situations. + + + +## [1.0.7](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/v1.0.6...v1.0.7) (2018-10-22) + + + + +## [1.0.6](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/compare/d314e83...v1.0.6) (2018-10-22) + + +### Bug Fixes + +* **project:** Add commit validation using husky and enable conventional changelog ([334bd20](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/334bd20)) + + +### Features + +* Install and create basic setup for Commitizen ([d314e83](https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/commits/d314e83)) diff --git a/LCU.WebApps.External.OnlineLoanApp/Dockerfile b/LCU.WebApps.External.OnlineLoanApp/Dockerfile new file mode 100644 index 0000000..d542565 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/Dockerfile @@ -0,0 +1,33 @@ +# docker build -t ola-ui . +# docker run --rm -p 8080:80 --name ola-ui ola-ui + +FROM node:14-bullseye AS build + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --unsafe-perm --before=2025-09-01 --ignore-scripts + +# Copy the source code. +COPY . . + +# Run scripts skipped during npm ci (generates src/environments/version.ts). +RUN npm run postinstall + +# Build the production bundle. Using npx avoids platform-specific npm script wrappers. +# Note: base-href/deploy-url set for hosting under /loanapp. +RUN npx ng build \ + --base-href="/loanapp/" \ + --deploy-url="/loanapp/" + +FROM nginx:1.25-alpine AS runtime + +COPY docker/nginx.conf /tmp/default.conf +RUN rm -f /etc/nginx/conf.d/default.conf && \ + mv /tmp/default.conf /etc/nginx/conf.d/default.conf + +COPY --from=build /app/dist/ui /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/LCU.WebApps.External.OnlineLoanApp/README.md b/LCU.WebApps.External.OnlineLoanApp/README.md new file mode 100644 index 0000000..ef1864f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/README.md @@ -0,0 +1,372 @@ +# Introduction +TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project. + +# Getting Started +TODO: Guide users through getting your code up and running on their own system. In this section you can talk about: +1. Installation process +2. Software dependencies +3. Latest releases +4. API references + +# Build and Test +TODO: Describe and show how to build your code and run the tests. + +# Contribute +TODO: Explain how other users and developers can contribute to make your code better. + +If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files: +- [ASP.NET Core](https://github.com/aspnet/Home) +- [Visual Studio Code](https://github.com/Microsoft/vscode) +- [Chakra Core](https://github.com/Microsoft/ChakraCore) +======= +# Landmark Credit Union Loan Application + +This readme outlines the developer process, basic coding style guide and provides guidance and details around the technical approach to the user interface. You should always review and update this document to encompass your changes. + +# Known Contacts + +The known contacts for this application are Jeff Knaub and Jerry Wenzen. + +# Getting Started +Before you can work with the code, build it, or deploy it you will need a proper developmenet environment. The required environment is basically Git, Node, and something to work with C#. +This environment code has been wrapped up into a Powershell script that should run on any Windows 10 machine and produce a viable development environment to carry you through this guide. +The script is available in the root of this directory as `setup-env.ps1`. + +You should start by reviewing and possibly running that script if needed. + +## Testing Notes on LandmarkCu.com / online banking + +The application is now integrated with Online Banking. Testing this is more difficult and involves having online banking +pointed at the dev application. The process flow is documented in the architecture documents, but also briefly included +here. + +1. User logs into online banking +2. User clicks the apply for loan button +3. Online banking POSTs a SAML token to the api end point. +4. If valid a redirect to the application is returned, and cookies will be attached. + +Cookies are required, and test online banking accounts are required. Additionally, there is a pipe from Azure to +on-premise so that we can call the core for member information. + +## Working With The Code +Code is checked out from the Azure DevOps Repository, which is currently [https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard] with the back end code +located at [https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizardAPI]. + +Work should be done in a branch from Develop. In the shared folder there is a software development process document that talks about this, you should start there. A brief introduction will be included here. + +Start by checking out the repository, and specifically the develop branch. You should be branching off of develop +with your own working branch. When you are complete, you will create a pull request within VSTS here: [https://landmarkcu.visualstudio.com/LoanWizard/_git/LoanWizard/pullrequests] + +Before submitting your pull request, review the following: + * Did you commit with commitizen? `git cz` (see below) + * Did you add all needed unit testing, and ensure all existing tests are passing? + * Have you run the e2e tests and ensured they all pass? + +In order to work and submit code, the project will need to be built and run. Instructions follow. Generally the project doesn't need to be built, just run locally. However, running a production build after major changes will ensure the production build is still working before CI runs. + +### Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. +You can also use npm to run these commands: +Run `npm run build` to build the project with default settings. +Run `npm run build-nvisia` to build the project with default settings. +Run `npm run build-release` to build the project with release settings (configuration for qa). +Run `npm run build-prod` to build the project with production settings (and build optimizer). + + +### Run (Development server) + +Run `npm run start` for a dev server. Navigate to `https://localhost:4200/`. The app will automatically reload if you change any of the source files. +Note that this will run with a proxy in place so that you can contact the web services. + +This start script is in the package.json file, and runs `ng serve --proxy-config proxy.conf.json --configuration=local --ssl` + +### Run (Develop, QA, Prod) + +Deployments to Develop, QQ, and Prod are automatically handled by Azure Dev-Ops. See the additional resources section below; as there is a document outlining the software development process, as well as the cloud setup Azure. + +### Running unit tests + +Run `npm run test` to run the unit tests with code coverage. +Run `npm run test-debug` to run the unit tests without code coverage, but with full stack traces. +Run `npm run test-interactive` if you want to use real chrome for debugging and not headless. +Firefox, IE, and Edge are also supported in this project. Run the ng test command and pass in the desired browser(s) into the browser argument +Example: ng test --watch=false --browsers Firefox,Chrome,IE,Edge --no-progress + +### Running end-to-end tests + +Tests run against Chrome and Firefox in parallel. +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +Run `npm run update-e2e` if someone has changed the protractor config and you are getting errors; this usually fixes the problem. You may also need +to run this if you are missing one of the browsers in the configuration. + +### Resources + There is additional documentation available within the Landmark shared folders; you should look for the following files, or ask someone for them if you can't find them. Start in the "Phase 1 Final Documents" folder under the 'LCU Credit App 2018' folder: + + * LCU Credit App 2018: This is a folder + * The following documents, plus additional documents should be available in this folder, get access to it as soon as possible. + * Architecture Document: Landmark Credit Union - Credit Application - SAD + * This is the software architecture description. + * Site Layout / Reference: Landmark_Site_Reference.XLSX + * This shows urls/locations for source code and deployables and the Azure configuration + * Software Process: landmark_software_process.docx + * Outlines the process for working with the source code. + * Azure Key Vault keys + * Outlines each key used by the API and it's purpose + * Additional notes on OLA's coding and components can also be found in the repo's wiki + * https://landmarkcu.visualstudio.com/LoanWizard/_wiki/wikis/Loan%20Wizard%20Wiki/ + +### Suggestion for IDE +* Visual Studio Code +* Webstorm + +# Architecture + + The best explanation for the architecture can be found by referencing the document listed in the resources section above. However if you are looking to do + something specific, here are some tips. + + A. Add a new loan type + 0. If you are adding a sub-type to an existing type, you do not need to follow all of these steps, these instructions cover adding a new category of loan type. + 0. See: loan-type.ts, credit-card-outline.ts, credit-card-routing.module.ts, application-wizard.service.ts, credit-application.service.ts, credit-application-dao.service.ts, api-translator.service.ts + 1. Begin in loan-type.ts file; add your type. + 2. Create an Outline, start by looking at credit-card-outline.ts. + 3. With your outline created, create a set of routes similar to credit-card-routing.module.ts + 4. Add any needed sets of fields to common-groups.ts + 5. Create a new pages in the loan-wizard/wizards folder. + 6. Create any new field groups needed in the loan-wizard/field-groups folder. + 7. If you need custom waypoints, that is in loan-wizard/controls folder. + 8. update the CreditApplicationService to support any new fields. + 9. update the ApplicationWizardService to support any new fields and pages. + 10. Update the api-translator for any fields that need to be submitted to the back end, this is in shared/api-translator. + 11. Add your loan to app/select-loan if needed. + 12. Make sure to add additional unit testing as needed for all new code. + 99. Don't forget you may need to change the back end services! + + B. Change an environment + 1. There is currently local, prod, e2e, and default. These are in src/environments, but you must also change angular.json!!!!! + + C. Add e2e testing + 1. This is under the e2e folder. + 2. There is a folder for each major loan type, and a shared folder for common pages. + 3. Add your folder and tests per the existing pattern, adding to shared when possible. + 4. In the protractor.conf.js file is where we specify which test files are run, and on which browsers. + 5. Don't forget to update your protractor install, there is a script in the package.json "npm run update-e2e" + + D. Something not listed here + 1. The file structure for this project is organized as a hybrid - most code is organized by feature, with shared code organized under + the shared folder. If you are looking for something with common behavior, start in app/shared. Otherwise it's best to start in app/loan-wizard. + 2. It's fairly easy to find the code for a specific page - use the route shown in your browser, like this: + > https://applydev.azurewebsites.net/loan-wizard/cc/st/step-2 + The 'CC' part is the category, and the 'ST' is the type. The 'step-2' is the page. + 1. Check the various routing modules to find your category (cc is credit card), this is *-routing.module.ts + 2. Find the route in that module corresponding to the page (step-2 in this case), the component listed is the code for + that page. Start there. In this case, CC leads to credit-card-routing.module.ts which leads to the PersonalInfoPageComponent. + 3. Beyond this, you should refer to the architectural document and spend some time reviewing the code. + + +# Releases + +Releases are handled by anyone on the team with access to VSTS. Generally when a pull request is accepted +to develop, that will be automatically built and deployed to DEV. The Dev application is here: [https://applydev.landmarkcu.com]. The +same is true for the back end / API. When you want to make a release, you must revise the changelog (CHANGELOG.md file). This +is a two step process: + 1. run 'npm run release' in the develop branch, this will: + A. Update the version in package.json automatically. + B. Update the CHANGELOG.md file automatically + 2. Update/Review the CHANGELOG.md as needed, usually for: + A. Add backend features/fixes/etc + B. Add missing ticket numbers (or 'no ticket') + C. Remove duplicates + +You can force a version number if needed by changing the package.json, package-log.json, and CHANGELOG.md. Once you are +done revising you can 'git commit --amend --no-edit' and then 'git push -f' will push the release to develop. + +To get to QA, develop must be merged into the release branch and pushed. This will automatically cause the release +branch to be build and the resulting release to be pushed to QA. + +To get to production, release must be merged into master and pushed. This will automatically cause the +code to be built and packaged as artifacts. However the release process is manual, and can be done +here: [https://landmarkcu.visualstudio.com/LoanWizard/_release] + +When creating releases, it's important not to forget to make a release and update the change notes as +needed. There is information on creating a release below. An important tip is that on the website +you can hover over the landmark copyright in the bottom left to see the released client version. + +## Setup Commitizen and Produce changelogs and release notes + +Commitizen is a command line tool that helps format commit messages with a series of prompts that are used to +generate a commit message. We use this tool to standardize our commits for tracking a changelog and producing release +notes. + + * `npm install commitizen -g` + * `npm install commitizen --save-dev` // Already done for you, skip. + * `npm install cz-conventional-changelog --save-dev` // Already done for you, skip. + * `npm install --save-dev validate-commit-msg` // Already done for you, skip. + * `npm install -g conventional-changelog-cli` + + Instead of committing with `git commit` commit with `git cz` + This will force your messages into the format (): + + Definition | Description + -----------|------------ + feat | A new feature + fix | A bug fix + docs | Documentation only changes + style | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) + refactor | A code change that neither fixes a bug nor adds a feature + perf | A code change that improves performance + test | Adding missing or correcting existing tests + chore | Changes to the build process or auxiliary tools and libraries such as documentation generation + + Here is how semantic-release will map these to release types and how it maps to [semantic verioning](https://semver.org/#summary): + + Commit message | Release type + ----------------|-------------- + `fix(pencil): stop graphite breaking `
`when too much pressure applied` | Patch Release (PATCH) + `feat(pencil): add 'graphiteWidth' option` | Feature Release (MINOR) + `BREAKING CHANGE: The graphiteWidth option `
`has been removed. The default graphite`
` width of 10mm is always used for performance reasons.` | Breaking Release (MAJOR) + + ## Produce or update the changelog + + You shouldn't need to do this, it should be generated for you when you create a release (below) + + * Create changelog from scratch: `conventional-changelog -p angular -i CHANGELOG.md -s -r 0` + * Update the changelog: `conventional-changelog -p angular -i CHANGELOG.md -s ` + + ## Produce a release + + * Use: `npm run release` + + Resources: + * http://www.nullfactory.net/2017/04/enforce-commit-message-standards-and-generate-changelogs-via-git/ + * https://dev.bleacherreport.com/how-we-use-commitizen-to-clean-up-commit-messages-a16790dcd2fd + * http://devboosts.com/2017/03/28/conventional-commits/ + * https://medium.com/@LeonardoBrAu/why-git-squash-and-rebase-commits-and-how-we-do-it-87aa17406e21 + +# ADA Compliance + + This application is targeting level AA ADA Compliance, here is the checklist you should keep in mind when developing: + + * Every image, video file, audio file, plug-in, etc. has an alt tag + * Complex graphics are accompanied by detailed text descriptions + * The alt descriptions describe the purpose of the objects + * If an image is also used as a link, the alt tag describes the graphics and the link destination + * Decorative graphics with no other function have empty alt descriptions (alt=””) + * Data tables have the column and row headers appropriately identified (using the tag) + * Make sure the page does not contain repeatedly flashing images + * Check to make sure the page does not contain a strobe effect + * All scripts and plug-ins and the content within them are accessible to assistive technologies, or else an alternative means of accessing equivalent content is provided + * When form controls are text input fields use the LABEL element + * When text is not available use the title attribute + * Include any special instructions within field labels + * Make sure that form fields are in a logical tab order + * Include a ‘Skip Navigation’ button to help those using text readers + * SVG icons should not focusable by tabbing in IE (utilize `focusable="false"` on all icon tags) + + ## Coding tools for ADA Compliance + * Refer to here for help: https://getbootstrap.com/docs/4.0/getting-started/accessibility/ + * Refer to here for practical examples: http://www.punkchip.com/aria-basic-findings/ + * use aria-label, aria-expanded, and aria-hidden as needed. + * Bootstrap: sr-only, bootstrap provides sr-only so that you can show things to people with screen readers. (sr-only stands for screen readers only) + +# Style Concerns + + 1) Make sure the validators listed are full and correct. + 2) Make sure the error display component shows all validators you are using. + 3) Make sure default value is provided at the model level. + 4) A field cannot be a number, be part of a select, and have blank ('') as the default. Make these non-numbers if possible. + 5) Select boxes should all start on their default, which should derive from the MODEL. + 6) Select boxes title/default should be "Please Choose" if they do not have a default value. + 7) Select boxes and text inputs should have class="form-control". + 8) every
declaration should have an `(ngSubmit)="commandNext()"`. + 9) Next button should be at the LEFT, the back button should be BEFORE it. + 10) Next button should say "Continue", not "Next". + + * Always keep the web development checklist in mind: http://webdevchecklist.com/ + * `(click)` bindings should bind to a CommandXXX function with Command as a prefix. This denotes to anyone looking at the component that a given function is + UX driven. + * Keep minimal functionality in Components, they should act as minimal controllers. Refactor work and business logic to a service. + * You should have unit tests for all components and services. + * Take your time with naming and ask the team for help; but take naming seriously so that your components and services are recognizable. + * Review the .editorconfig file if you are notices style differences in PRs, resolve any problems with the team. + * DO NOT use angular material for the loan application system! Angular material should only be used for the admin system. + +# Libraries + + * https://github.com/ngx-translate/core + +## Admin System Dependencies + * @angular/cdk + * @angular/material + +## Angular Material + * Angular material is a library used exclusively for the admin system. + * Provides the datatable with native sorting/paging/filtering + +## Angular +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.8 with the command `ng new ui --routing --style scss` +The project's package.json has been updated with various libraries and scripts; it should always be reviewed. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. +We do not generally use the code scaffolding. + +# FAQ + +Q. I'm running the unit test and I see output like this: +`HeadlessChrome 68.0.3440 (Windows 10.0.0) XYZComponent should use the service to add address controls to the form group FAILED + [object ErrorEvent] thrown +` + +A. Re-run the tests using `npm run test-debug`, which will show the full stack trace but not provide code coverage. + +Q. What do I need to know about NGX-Translate + +A. The primary translation file is en.json, and is loaded asynchronously (it could be moved out of project); this is setup in the app.module.ts by + using a TranslateHttpLoader. Some entries have HTML, if you want to use HTML in an entry, you have to bind to [innerHtml] + Html example: `
` + non-html example `
{{'COPY.DISCLOSURES' | translate: localState}}
` + You should refer to the ngx-translate documentation for full usage and instructions. + +Q. What do I need to know about Security? + +A. There are many security concerns outlines in the architecture documents in the share (see resources above). To add to it, there are multiple security mechanisms at play. One is for Landmark employees, and uses the ADAL-Angular-6 library to authenticate against Azure AD. The other is with NCR/DI and is used to authenticate members; this is handld in the API and the client uses this via checking for cookies. Be careful not to mix up these two methods. + +# File Upload Component and Azure Blob Storage +The file upload component allows users and dealerships to upload files to ACTion. This is handled with both Azure Blob Storage and CosmosDB. CosmosDB has a list of files associated with a loan application that contains information about the file and the GUID of the file in Blob Storage. + +When an application is submitted or a dealership clicks transmit, the LoanApplicationController will get the CosmosDB record for the list of files and loop over them to transmit them to ACTion. After the files have been transmitted to ACTion, they are removed from Blob Storage. + +The files that aren't ever transmitted to ACTion will remain in Blob Storage (this can happen from API failure or if a dealership closes the form without transmitting). Files that are older than 30 days will be removed automatically by a rule set in the Lifecycle section of Azure. + +# Dealership Portal +The dealership portal can be accessed via ```/dealership-portal``` and allows dealerships to upload documents to an ACTion loan. There are four required fields that are used to authenticate a dealership: dealership code, dealership password, application ID, and the primary applicant's last name/full business name. + +## Dealership Code +```(string, case-sensitive, max length: 60)``` + +2-3 digit code that is automatically generated by ACTion when a dealership is added. It takes the first letter of the dealership's name and then increments the number based on other dealerships with the same first letter. So if there were 9 dealerships that started with B (```B1-B9```), the next B-named dealership would become ```B10```. + +It also appears that it converts dealerships that begin with a number to the first letter of that number, e.g. ```5``` -> ```F```; so ```5 Corners Dodge``` becomes ```F6```, as it is the 6th dealership to begin with ```F```. + +The dealership code can be found within ACTion. +## Dealership Password +```(string, case-sensitive, max-length 30)``` + +This password is not a password in the traditional sense, as it is being stored in the plain-text field ```costCenterCode``` within ACTion. Dealership's will not have access to create/change their password, so it must be done by someone at Landmark with access to ACTion. +## Application ID +```(integer)``` + +Self-explanatory +## Primary Applicant's Last Name or Business Full Name +```(string, case-insensitive, max-length: 250)``` + +When the authentication request is made, the application is taken from ACTion, the ```primaryBorrower``` field is inspected for ```idRef``` which contains the GUID of the primary borrower. The primary borrower can be an individual or a company. Once this ID is found, the API loops over the individuals and companies associated with the application until it finds the matching ID. + +Once the matching ID is found, it will return the ```lastName``` if it is an individual or the ```tradeName``` if it is a company. The request much match this value exactly, but is not case-sensitive. + +## Other notes +This portal utilizes the file upload component, which is the same component used for the loan application process. Any changes to this component that is specific to the dealership portal must be made with caution as those changes will affect all loan processes that use it. + +In non-production environments, you can force an error to happen to test what happens if a file transmission error occurs by uploading the first file with the name ```do-not-allow-transmission.jpg```. The API will see this file and return a ```500```. diff --git a/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/install_dependencies.ps1 b/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/install_dependencies.ps1 new file mode 100644 index 0000000..8b1994d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/install_dependencies.ps1 @@ -0,0 +1 @@ +cinst -y kind diff --git a/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/kind.cluster.config b/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/kind.cluster.config new file mode 100644 index 0000000..b33f889 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/kind.cluster.config @@ -0,0 +1,7 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: +- role: control-plane + extraPortMappings: + - containerPort: 30000 + hostPort: 80 diff --git a/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/setup_and_deploy.ps1 b/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/setup_and_deploy.ps1 new file mode 100644 index 0000000..dd4b4a4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/setup_and_deploy.ps1 @@ -0,0 +1,17 @@ +kind create cluster --config kind.cluster.config --wait 300s + +kubectl create namespace landmarkcu + +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +helm repo update + +helm install my-release ingress-nginx/ingress-nginx ` + --set controller.service.type=LoadBalancer ` + --set controller.service.nodePorts.http=30000 ` + --set controller.service.nodePorts.https=30001 + +docker build -t local/online-loan-app:1.0 .. + +kind load docker-image local/online-loan-app:1.0 + +kubectl apply -k ..\_deploy\overlays\local diff --git a/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/teardown.ps1 b/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/teardown.ps1 new file mode 100644 index 0000000..64866e8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/_kube_local_testing/teardown.ps1 @@ -0,0 +1 @@ +kind delete cluster diff --git a/LCU.WebApps.External.OnlineLoanApp/angular.json b/LCU.WebApps.External.OnlineLoanApp/angular.json new file mode 100644 index 0000000..2ee9f0b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/angular.json @@ -0,0 +1,174 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "ui": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "prefix": "app", + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/ui", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets", + "src/web.config", + "src/Robots.txt" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [ + "./node_modules/@fortawesome/fontawesome/index.js", + "./node_modules/@fortawesome/fontawesome-free-solid/index.js", + "./node_modules/@fortawesome/fontawesome-free-regular/index.js" + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true + }, + "configurations": { + "production": {}, + "local": { + "optimization": false, + "outputHashing": "none", + "sourceMap": true, + "extractCss": false, + "namedChunks": true, + "aot": false, + "extractLicenses": false, + "vendorChunk": true, + "buildOptimizer": false + }, + "dev": { + "optimization": false, + "outputHashing": "none", + "sourceMap": true, + "extractCss": false, + "namedChunks": true, + "aot": false, + "extractLicenses": false, + "vendorChunk": true, + "buildOptimizer": false + }, + "e2e": { + "optimization": false, + "outputHashing": "none", + "sourceMap": true, + "extractCss": false, + "namedChunks": true, + "aot": false, + "extractLicenses": false, + "vendorChunk": true, + "buildOptimizer": false + }, + "qa": {}, + "uat": {} + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "ui:build" + }, + "configurations": { + "production": { + "browserTarget": "ui:build:production" + }, + "local": { + "browserTarget": "ui:build:local" + }, + "uat": { + "browserTarget": "ui:build:uat" + }, + "e2e": { + "browserTarget": "ui:build:e2e" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "ui:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + "src/styles.scss" + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "ui-e2e": { + "root": "e2e/", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "ui:serve:e2e" + }, + "configurations": { + "production": { + "devServerTarget": "ui:serve:production" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + } + }, + "defaultProject": "ui" +} diff --git a/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-k8s-pipeline.yaml b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-k8s-pipeline.yaml new file mode 100644 index 0000000..6ca9285 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-k8s-pipeline.yaml @@ -0,0 +1,53 @@ +parameters: +- name: target_environment + displayName: Target Environment + type: string + default: qa + values: + - qa + - uat + - prd +- name: k8s_namespace + displayName: K8s Namespace to deploy to + type: string + default: landmarkcu + values: + - landmarkcu + +variables: +- template: ./config-files/${{ parameters.target_environment }}-pvars.yaml +- name: source_branch_name + value: $[replace(replace(variables['Build.SourceBranch'], 'refs/heads/', ''), '/', '-')] +- name: docker_container_repository_name + value: landmarkcu/lcu.api.internal.onlineloanapp +resources: + repositories: + - repository: azdo_pipeline_templates + type: git + name: Infrastructure Platform/azdo-pipeline-templates + ref: main + +stages: +# DOCKER BUILD AND PUSH +- template: docker-build-push.yaml@azdo_pipeline_templates + parameters: + azdo_agent_pool: $(pvar_azdo_agent_pool) + docker_registry_service_connection: $(pvar_docker_registry_service_connection) + docker_container_repository_name: $(docker_container_repository_name) + dockerfile_path: ./Dockerfile + docker_image_tags: | + build-$(Build.BuildId) + $(source_branch_name) + +# K8S DEPLOY +- template: k8s-deploy.yaml@azdo_pipeline_templates + parameters: + dependsOn: CI + condition: or(eq(variables['Build.SourceBranchName'], 'develop'), eq(variables['Build.SourceBranchName'], 'main')) + azdo_agent_pool: $(pvar_azdo_agent_pool) + kubectl_version: $(pvar_kubectl_version) + k8s_service_connection: $(pvar_k8s_service_connection) + k8s_namespace: ${{ parameters.k8s_namespace }} + k8s_kustomization_path: ./kustomize/overlays/${{ parameters.target_environment }} + docker_registry_service_connection: $(pvar_docker_registry_service_connection) + docker_image_to_deploy: $(docker_container_repository_name):build-$(Build.BuildId) diff --git a/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-feature-branches.yaml b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-feature-branches.yaml new file mode 100644 index 0000000..f63f0b5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-feature-branches.yaml @@ -0,0 +1,10 @@ +trigger: + branches: + include: + - '*' + exclude: + - 'main' + - 'develop' + +extends: + template: azure-k8s-pipeline.yaml diff --git a/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-prd.yaml b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-prd.yaml new file mode 100644 index 0000000..8e66ed4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-prd.yaml @@ -0,0 +1,10 @@ +trigger: + branches: + include: + - 'main' + +extends: + template: azure-k8s-pipeline.yaml + parameters: + target_environment: prd + k8s_namespace: landmarkcu diff --git a/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-qa.yaml b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-qa.yaml new file mode 100644 index 0000000..30ce939 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-qa.yaml @@ -0,0 +1,10 @@ +trigger: + branches: + include: + - 'develop' + +extends: + template: azure-k8s-pipeline.yaml + parameters: + target_environment: qa + k8s_namespace: landmarkcu diff --git a/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-uat.yaml b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-uat.yaml new file mode 100644 index 0000000..e33f5d3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/azure-pipelines-uat.yaml @@ -0,0 +1,10 @@ +trigger: + branches: + include: + - 'main' + +extends: + template: azure-k8s-pipeline.yaml + parameters: + target_environment: uat + k8s_namespace: landmarkcu diff --git a/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/prd-pvars.yaml b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/prd-pvars.yaml new file mode 100644 index 0000000..9b650cd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/prd-pvars.yaml @@ -0,0 +1,7 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-prd-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-prd-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem diff --git a/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/qa-pvars.yaml b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/qa-pvars.yaml new file mode 100644 index 0000000..dfc1bfb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/qa-pvars.yaml @@ -0,0 +1,7 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-qa-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-qa-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem diff --git a/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/uat-pvars.yaml b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/uat-pvars.yaml new file mode 100644 index 0000000..10d7ce3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/azdo_pipelines/config-files/uat-pvars.yaml @@ -0,0 +1,7 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-uat-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-uat-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem diff --git a/LCU.WebApps.External.OnlineLoanApp/docker/nginx.conf b/LCU.WebApps.External.OnlineLoanApp/docker/nginx.conf new file mode 100644 index 0000000..99cf224 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/docker/nginx.conf @@ -0,0 +1,24 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Serve the app under /loanapp, stripping the prefix for static assets and + # falling back to the SPA entrypoint for deep links. + location /loanapp/ { + rewrite ^/loanapp/(.*)$ /$1 break; + try_files $uri $uri/ /index.html; + } + + # Fallback for any other paths (e.g., health checks). + location / { + try_files $uri $uri/ /index.html; + } + + # Basic security headers. + add_header X-Content-Type-Options "nosniff"; + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/protractor.conf.js b/LCU.WebApps.External.OnlineLoanApp/e2e/protractor.conf.js new file mode 100644 index 0000000..3142a02 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/protractor.conf.js @@ -0,0 +1,35 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/credit-card/*.e2e-spec.ts', + './src/select-loan/*.e2e-spec.ts', + './src/heloc/*.e2e-spec.ts', + './src/vehicle/*.e2e-spec.ts', + './src/signature/*.e2e-spec.ts' + ], + multiCapabilities: [{ + 'browserName': 'chrome' + }, { + 'browserName': 'firefox' + }], + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.e2e.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + // Do not do this! browser.waitForAngularEnabled(false); + } +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-full-spouses.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-full-spouses.e2e-spec.ts new file mode 100644 index 0000000..680ff28 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-full-spouses.e2e-spec.ts @@ -0,0 +1,153 @@ +import { browser, by, element, ElementFinder, protractor } from 'protractor'; +import { CreditCardPageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { CreditCardDisclosuresPagePo } from './pages/page-disclosures.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { SpouseTypePagePo } from '../shared/pages/spouse-type-page.po'; + +describe('It should create a credit card application for 4 full info individuals', () => { + + it('should accept maximum inputs for page 1 (Member/Loan Type)', () => { + const page = new CreditCardPageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.ALL_FIELDS, ...page.HIDDEN_FIELDS_1]); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length + page.HIDDEN_FIELDS_1.length); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 2.A (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsPrimaryMarriedButNotMarriedToCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.A (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalLivingInWI(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.A (Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept FULL INFO SPOUSE input for Applicant spouse type page', () => { + const page = new SpouseTypePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsFullInfoSpouse(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 2.B (Applicant\'s Spouse Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalNoMarital(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.B (Applicant\'s Spouse Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressSame(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.B (Applicant\'s Spouse Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2.C (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicantNonPrimary(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.C (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalLivingInWI(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.C (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept FULL INFO SPOUSE input for CoApplicant spouse type page', () => { + const page = new SpouseTypePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsFullInfoSpouse(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 2.D (CoApplicant\'s Spouse Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalNoMarital(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.D (CoApplicant\'s Spouse Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressSame(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.D (CoApplicant\'s Spouse Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should maximum inputs for page 5 (Disclosures)', () => { + const page = new CreditCardDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-go-back.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-go-back.e2e-spec.ts new file mode 100644 index 0000000..00953b3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-go-back.e2e-spec.ts @@ -0,0 +1,129 @@ +import { browser, by, element, ElementFinder, protractor } from 'protractor'; +import { CreditCardPageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { CreditCardDisclosuresPagePo } from './pages/page-disclosures.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { WizardPagePo } from '../shared/pages/wizard-page.po'; + +describe('It should allow free navigation of a completed credit card application', () => { + + it('should accept maximum inputs for page 1.', () => { + const page = new CreditCardPageOnePo(); + browser.get(page.PAGE_URL); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + page.fillFieldsMaximumCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + page.showEmploymentFields(); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + expect(page.canGoNext()).toEqual(true); + // Will no longer happen. + // page.fillSecondIncome(); + // expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2.A (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3.A (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressNotSame(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4.A (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + // Will no longer happen. + // expect(page.canGoNext()).toEqual(false); + // page.fillSecondIncome(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should maximum inputs for page 5 (Disclosures)', () => { + const page = new CreditCardDisclosuresPagePo(); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + + it('should jump back to the beginning.', () => { + const urlList = [ + 'http://localhost:4200/loan-wizard/cc/cb/co-applicant-financial', + 'http://localhost:4200/loan-wizard/cc/cb/co-applicant-residential', + 'http://localhost:4200/loan-wizard/cc/cb/co-applicant-personal', + 'http://localhost:4200/loan-wizard/cc/cb/step-4', + 'http://localhost:4200/loan-wizard/cc/cb/step-3', + 'http://localhost:4200/loan-wizard/cc/cb/step-2', + 'http://localhost:4200/loan-wizard/cc/cb/step-1' + ]; + const page = new WizardPagePo(); + urlList.forEach((eachUrl) => { + expect(page.canGoPrevious()).toEqual(true); + page.goPrevious(); + browser.getCurrentUrl().then((newUrl) => { + expect(newUrl).toEqual(eachUrl); + }); + expect(page.canGoNext()).toEqual(true); + + }, this); + + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-maximum.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-maximum.e2e-spec.ts new file mode 100644 index 0000000..bd8a134 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-maximum.e2e-spec.ts @@ -0,0 +1,144 @@ +import { browser, by, element, ElementFinder, protractor } from 'protractor'; +import { CreditCardPageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { CreditCardDisclosuresPagePo } from './pages/page-disclosures.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; + +describe('It should create a maximum credit card application ', () => { + + it('should accept maximum inputs for page 1 (Member/Loan Type)', () => { + const page = new CreditCardPageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.ALL_FIELDS, ...page.HIDDEN_FIELDS_1]); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length + page.HIDDEN_FIELDS_1.length); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + // page.markSingleIncomeNotEnough(); + // Will no longer happen. + // expect(page.canGoNext()).toEqual(false); + // page.fillSecondIncome(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2.A (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + const EC = protractor.ExpectedConditions; + browser.wait(EC.visibilityOf(page.getFirstElement()), 5000).then(() => {}); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.COAPP_FIELDS); + expect(inputArray.length).toEqual(page.COAPP_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicantNonPrimary(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3.A (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressNotSame(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4.A (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + // Will no longer happen. + // expect(page.canGoNext()).toEqual(false); + // page.fillSecondIncome(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should maximum inputs for page 5 (Disclosures)', () => { + const page = new CreditCardDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-minimal.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-minimal.e2e-spec.ts new file mode 100644 index 0000000..31a5adb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-minimal.e2e-spec.ts @@ -0,0 +1,85 @@ +import { browser, by, element, ElementFinder, protractor } from 'protractor'; +import { CreditCardPageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { CreditCardDisclosuresPagePo } from './pages/page-disclosures.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; + +describe('It should create a minimal credit card application ', () => { + + it('should accept basic inputs for page 1 (Member/Loan Type)', () => { + const page = new CreditCardPageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.fillFieldsForFailureToConfirmJointCredit(); + expect(page.canGoNext()).toEqual(false); + page.dismissModalByOkayButton(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + // For a minimal credit card, don't enter any income :-) + // expect(page.canGoNext()).toEqual(false); + // page.showEmploymentFields(); + // inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + // expect(inputArray.length).toEqual([...page.EMPLOYMENT_FIELDS].length); + // expect(page.canGoNext()).toEqual(false); + // page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 5 (Disclosures)', () => { + const page = new CreditCardDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + + // We could go to the next page, but we'd create X loans every time we run these tests... +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-tattle-spouses.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-tattle-spouses.e2e-spec.ts new file mode 100644 index 0000000..801b485 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/credit-card-tattle-spouses.e2e-spec.ts @@ -0,0 +1,130 @@ +import { browser, by, element, ElementFinder, protractor } from 'protractor'; +import { CreditCardPageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { CreditCardDisclosuresPagePo } from './pages/page-disclosures.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { SpouseTypePagePo } from '../shared/pages/spouse-type-page.po'; +import { TattleSpousePagePo } from '../shared/pages/tattle-spouse-page.po'; + +describe('It should create a credit card application for 2 full info individuals + 2 tattle spouses', () => { + + it('should accept maximum inputs for page 1 (Member/Loan Type)', () => { + const page = new CreditCardPageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.ALL_FIELDS, ...page.HIDDEN_FIELDS_1]); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length + page.HIDDEN_FIELDS_1.length); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 2.A (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsPrimaryMarriedButNotMarriedToCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.A (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalLivingInWI(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.A (Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept TATTLE SPOUSE input for Applicant spouse type page', () => { + const page = new SpouseTypePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsTattleSpouse(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for tattle spouse page (Applicant\'s Spouse Tattle Info)', () => { + const page = new TattleSpousePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + + it('should accept maximum inputs for page 2.C (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicantNonPrimary(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.C (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalLivingInWI(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.C (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept TATTLE SPOUSE input for CoApplicant spouse type page', () => { + const page = new SpouseTypePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsTattleSpouse(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for tattle spouse page (CoApplicant\'s Spouse Tattle Info)', () => { + const page = new TattleSpousePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should maximum inputs for page 5 (Disclosures)', () => { + const page = new CreditCardDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/pages/page-disclosures.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/pages/page-disclosures.po.ts new file mode 100644 index 0000000..11f9426 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/pages/page-disclosures.po.ts @@ -0,0 +1,34 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; +import { PageObjectTools } from '../../shared/page-object-tools'; + +export class CreditCardDisclosuresPagePo { + + public readonly PAGE_URL = '/loan-wizard/cc/cb/disclosures'; + + public readonly DISCLOSE_TRUTHFUL = 'truthful'; + public readonly DISCLOSE_CONTACT = 'contact'; + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly ALL_FIELDS = [ + this.DISCLOSE_TRUTHFUL, this.DISCLOSE_CONTACT, + this.NEXT_BUTTON]; + + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + const truthful = element(by.id(this.DISCLOSE_TRUTHFUL)); + PageObjectTools.clickCheckBox(truthful); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/pages/page-one.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/pages/page-one.po.ts new file mode 100644 index 0000000..79d3093 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/credit-card/pages/page-one.po.ts @@ -0,0 +1,77 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; +import { PageObjectTools } from '../../shared/page-object-tools'; +import { TestInputSet, TestInputCommand, TestingInputType } from '../../shared/data-driven/test-input-command'; + +export class CreditCardPageOnePo { + + public readonly PAGE_URL = '/loan-wizard/cc/cb/step-1'; + + public readonly LIMIT_REQUESTED = 'inputLimitRequested'; + public readonly ANOTHER_PERSON = 'inputApplicantType'; + public readonly LIVE_IN_WI = 'inputLivesInMaritalPropertyState'; + public readonly JOINT_CREDIT = 'inputConfirmJointCredit'; + public readonly CO_APP_LIVE_IN_WI = 'inputCoAppLivesInMaritalPropertyState'; + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly MODAL_CLOSE_BUTTON = 'modalCloseButton'; + public readonly MODAL_OKAY_BUTTON = 'modalOkayButton'; + public readonly ALL_FIELDS = [this.LIMIT_REQUESTED, this.ANOTHER_PERSON, this.LIVE_IN_WI, this.NEXT_BUTTON]; + public readonly HIDDEN_FIELDS_1 = [this.JOINT_CREDIT, this.CO_APP_LIVE_IN_WI]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.LIMIT_REQUESTED, fieldValue: 1234, fieldType: TestingInputType.TEXT }, + { fieldId: this.ANOTHER_PERSON, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.LIVE_IN_WI, fieldValue: 1, fieldType: TestingInputType.RADIO } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + { fieldId: this.LIMIT_REQUESTED, fieldValue: 1234, fieldType: TestingInputType.TEXT }, + { fieldId: this.ANOTHER_PERSON, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.LIVE_IN_WI, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.JOINT_CREDIT, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.CO_APP_LIVE_IN_WI, fieldValue: 0, fieldType: TestingInputType.RADIO } + ]; + + public readonly JOINT_CREDIT_DATA: TestInputSet = [ + { fieldId: this.ANOTHER_PERSON, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.JOINT_CREDIT, fieldValue: 1, fieldType: TestingInputType.RADIO } + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsForFailureToConfirmJointCredit(): void { + PageObjectTools.fillFields(this.JOINT_CREDIT_DATA); + } + + public dismissModalByOkayButton(): void { + const modalOkayButton = element(by.id(this.MODAL_OKAY_BUTTON)); + modalOkayButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA); + } + + public getFocusedElement() { + return PageObjectTools.getFocusedElement(); + } + + public getFirstElement() { + return PageObjectTools.getElementsIdField(this.LIMIT_REQUESTED); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/heloc-maximum.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/heloc-maximum.e2e-spec.ts new file mode 100644 index 0000000..b1618b2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/heloc-maximum.e2e-spec.ts @@ -0,0 +1,177 @@ +import { browser, protractor } from 'protractor'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { HelocPageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { HmdaPagePo } from '../shared/pages/hmda-page.po'; +import { CollateralPagePo } from '../shared/pages/collateral-page.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { CreditCardDisclosuresPagePo } from '../credit-card/pages/page-disclosures.po'; + +describe('It should create a maximum HELOC application ', () => { + + it('should accept maximum inputs for page 1 (Member/Loan Type)', () => { + const page = new HelocPageOnePo(); + browser.get(page.PAGE_URL); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for HMDA page (HMDA: Race/Ethnicity/Gender)', () => { + const page = new HmdaPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + + it('should accept maximum inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS, page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.STARTING_FIELDS, ...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.STARTING_FIELDS, ...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + // Will no longer happen. + // expect(page.canGoNext()).toEqual(false); + // page.fillSecondIncome(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2.A (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + const EC = protractor.ExpectedConditions; + browser.wait(EC.visibilityOf(page.getFirstElement()), 5000).then(() => {}); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.COAPP_FIELDS); + expect(inputArray.length).toEqual(page.COAPP_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicantNonPrimary(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + + it('should accept maximum inputs for page 3.A (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressNotSame(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for co-applicant HMDA page (Co-applicant HMDA: Race/Ethnicity/Gender)', () => { + const page = new HmdaPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4.A (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + // Will no longer happen. + // expect(page.canGoNext()).toEqual(false); + // page.fillSecondIncome(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for collateral page (Collateral)', () => { + const page = new CollateralPagePo(); + const EC = protractor.ExpectedConditions; + browser.wait(EC.visibilityOf(page.getFirstElement()), 5000).then(() => {}); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.ALL_FIELDS, ...page.ADDRESS_FIELDS, ...page.LIEN_POSITION_FIELDS]); + expect(inputArray.length).toEqual([...page.ALL_FIELDS, ...page.ADDRESS_FIELDS, ...page.LIEN_POSITION_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 5 (Disclosures)', () => { + const page = new CreditCardDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/heloc-minimal.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/heloc-minimal.e2e-spec.ts new file mode 100644 index 0000000..e65e678 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/heloc-minimal.e2e-spec.ts @@ -0,0 +1,99 @@ +import { browser, by, element, ElementFinder, protractor } from 'protractor'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { HelocPageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { HmdaPagePo } from '../shared/pages/hmda-page.po'; +import { CollateralPagePo } from '../shared/pages/collateral-page.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { CreditCardDisclosuresPagePo } from '../credit-card/pages/page-disclosures.po'; + +describe('It should create a minimal HELOC application ', () => { + + it('should accept basic inputs for page 1 (Member/Loan Type)', () => { + const page = new HelocPageOnePo(); + browser.get(page.PAGE_URL); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 3.A (HMDA: Race/Ethnicity/Gender)', () => { + const page = new HmdaPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS, page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.STARTING_FIELDS, ...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.STARTING_FIELDS, ...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + + it('should accept basic inputs for page 4.A (Collateral)', () => { + const page = new CollateralPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + + it('should accept basic inputs for page 5 (Disclosures)', () => { + const page = new CreditCardDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/pages/page-one.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/pages/page-one.po.ts new file mode 100644 index 0000000..86f07a5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/heloc/pages/page-one.po.ts @@ -0,0 +1,56 @@ +import { by, element, promise } from 'protractor'; +import { PageObjectTools } from '../../shared/page-object-tools'; +import { TestInputSet, TestingInputType } from '../../shared/data-driven/test-input-command'; + +export class HelocPageOnePo { + + public readonly PAGE_URL = '/loan-wizard/eq/he/step-1'; + + public readonly LIMIT_REQUESTED = 'inputLimitRequested'; + public readonly ANOTHER_PERSON = 'inputApplicantType'; + public readonly LIVE_IN_WI = 'inputLivesInMaritalPropertyState'; + public readonly JOINT_CREDIT = 'inputConfirmJointCredit'; + public readonly CO_APP_LIVE_IN_WI = 'inputCoAppLivesInMaritalPropertyState'; + public readonly PURPOSE_OF_LOAN = 'inputPurposeOfLoan'; + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly ALL_FIELDS = [this.LIMIT_REQUESTED, this.ANOTHER_PERSON, this.LIVE_IN_WI, this.NEXT_BUTTON, this.PURPOSE_OF_LOAN]; + public readonly HIDDEN_FIELDS_1 = [this.JOINT_CREDIT, this.CO_APP_LIVE_IN_WI]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.LIMIT_REQUESTED, fieldValue: 5432, fieldType: TestingInputType.TEXT }, + { fieldId: this.ANOTHER_PERSON, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.LIVE_IN_WI, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.PURPOSE_OF_LOAN, fieldValue: 1, fieldType: TestingInputType.SELECT } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + { fieldId: this.LIMIT_REQUESTED, fieldValue: 5432, fieldType: TestingInputType.TEXT }, + { fieldId: this.ANOTHER_PERSON, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.LIVE_IN_WI, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.JOINT_CREDIT, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.CO_APP_LIVE_IN_WI, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.PURPOSE_OF_LOAN, fieldValue: 2, fieldType: TestingInputType.SELECT } + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-credit.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-credit.po.ts new file mode 100644 index 0000000..ff992be --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-credit.po.ts @@ -0,0 +1,10 @@ +import { SelectLoanPo } from './select-loan.po'; + +export class SelectLoanCreditPo extends SelectLoanPo { + + public readonly PAGE_URL = '/select-loan/credit-card'; + public readonly CASH_BACK = 'cc-cash'; + public readonly REWARDS = 'cc-reward'; + public readonly STUDENT = 'cc-student'; + public readonly SECURED = 'cc-secured'; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-home.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-home.po.ts new file mode 100644 index 0000000..6722d31 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-home.po.ts @@ -0,0 +1,11 @@ +import { SelectLoanPo } from './select-loan.po'; + +export class SelectLoanHomePo extends SelectLoanPo { + + public readonly PAGE_URL = '/select-loan/home-equity'; + public readonly HELOC = 'eq-heloc'; + public readonly HOME_EQUITY = 'eq-fixed'; + public readonly MORT_REFI = 'eq-refi'; + public readonly MANU_HOME = 'eq-manu'; + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-personal.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-personal.po.ts new file mode 100644 index 0000000..28b9d57 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-personal.po.ts @@ -0,0 +1,7 @@ +import { SelectLoanPo } from './select-loan.po'; + +export class SelectLoanPersonalPo extends SelectLoanPo { + + public readonly PAGE_URL = '/loan-wizard/sl/pe'; + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-vehicle-sub.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-vehicle-sub.po.ts new file mode 100644 index 0000000..227f2f6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-vehicle-sub.po.ts @@ -0,0 +1,9 @@ +import { SelectLoanPo } from './select-loan.po'; + +export class SelectLoanVehicleSubPo extends SelectLoanPo { + + public PAGE_URL = '/select-loan/sub-vehicle'; + public readonly LOAN = 've-loan'; + public readonly PRE = 've-pre'; + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-vehicle.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-vehicle.po.ts new file mode 100644 index 0000000..3729231 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan-vehicle.po.ts @@ -0,0 +1,11 @@ +import { SelectLoanPo } from './select-loan.po'; + +export class SelectLoanVehiclePo extends SelectLoanPo { + + public readonly PAGE_URL = '/select-loan/vehicle'; + public readonly AUTO = 've-sub'; + public readonly BOAT = 've-boat'; + public readonly POWERSPORTS = 've-power'; + public readonly TRAILER = 've-trailer'; + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan.po.ts new file mode 100644 index 0000000..5e837fd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/pages/select-loan.po.ts @@ -0,0 +1,42 @@ +import {browser, by, element } from 'protractor'; + +export class SelectLoanPo { + + public PAGE_URL = '/'; + public readonly CREDIT_CARD_CARD = 'cc-card'; + public readonly HOME_EQUITY_CARD = 'he-card'; + public readonly VEHICLE_CARD = 've-card'; + public readonly PERSONAL_CARD = 'pe-card'; + public readonly PREVIOUS_BUTTON = 'previous'; + + + navigateTo() { + return browser.get(this.PAGE_URL); + } + + getHeaderBar() { + return element(by.css('lw-navigation')); + } + + getPageTitle() { + return browser.getTitle(); + } + + getTextByClassName(className: string) { + return element(by.className(className)).getText(); + } + + isElementEnabled(eId: string) { + return element(by.id(eId)).isEnabled(); + } + + clickCard(eId: string): void { + const card = element(by.id(eId)); + card.click(); + } + + goPrevious(): void { + const nextButton = element(by.id(this.PREVIOUS_BUTTON)); + nextButton.click(); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/select-loan.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/select-loan.e2e-spec.ts new file mode 100644 index 0000000..1d03a77 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/select-loan/select-loan.e2e-spec.ts @@ -0,0 +1,107 @@ +import { SelectLoanPo } from './pages/select-loan.po'; +import { SelectLoanCreditPo } from './pages/select-loan-credit.po'; +import { browser } from 'protractor'; +import { SelectLoanVehiclePo } from './pages/select-loan-vehicle.po'; +import { SelectLoanHomePo } from './pages/select-loan-home.po'; +import { SelectLoanPersonalPo } from './pages/select-loan-personal.po'; +import { SelectLoanVehicleSubPo } from './pages/select-loan-vehicle-sub.po'; + +describe('Select Loan', () => { + +const landingPage = new SelectLoanPo(); + + it('should display landing page with correct text and cards', () => { + landingPage.navigateTo(); + expect(landingPage.getHeaderBar()).toBeDefined(); + const text = landingPage.getTextByClassName('loan-header'); + expect(text).toEqual('Start a new loan application'); + expect(landingPage.isElementEnabled(landingPage.CREDIT_CARD_CARD)).toEqual(true); + expect(landingPage.isElementEnabled(landingPage.HOME_EQUITY_CARD)).toEqual(true); + expect(landingPage.isElementEnabled(landingPage.PERSONAL_CARD)).toEqual(true); + expect(landingPage.isElementEnabled(landingPage.VEHICLE_CARD)).toEqual(true); + landingPage.clickCard(landingPage.CREDIT_CARD_CARD); + }); + + it('should display credit card landing page with correct text and cards', () => { + const page = new SelectLoanCreditPo(); + expect(page.getHeaderBar()).toBeDefined(); + const text = page.getTextByClassName('loan-header'); + expect(text).toEqual('Select a credit card type'); + expect(page.isElementEnabled(page.CASH_BACK)).toEqual(true); + expect(page.isElementEnabled(page.REWARDS)).toEqual(true); + expect(page.isElementEnabled(page.SECURED)).toEqual(true); + expect(page.isElementEnabled(page.STUDENT)).toEqual(true); + page.clickCard(page.CASH_BACK); + browser.navigate().back(); + page.clickCard(page.REWARDS); + browser.navigate().back(); + page.clickCard(page.SECURED); + browser.navigate().back(); + page.clickCard(page.STUDENT); + browser.navigate().back(); + page.goPrevious(); + landingPage.clickCard(landingPage.VEHICLE_CARD); + }); + + it('should display vehicle landing page with correct text and cards', () => { + const page = new SelectLoanVehiclePo(); + expect(page.getHeaderBar()).toBeDefined(); + const text = page.getTextByClassName('loan-header'); + expect(text).toEqual('Select a vehicle type'); + expect(page.isElementEnabled(page.BOAT)).toEqual(true); + expect(page.isElementEnabled(page.POWERSPORTS)).toEqual(true); + expect(page.isElementEnabled(page.TRAILER)).toEqual(true); + expect(page.isElementEnabled(page.AUTO)).toEqual(true); + page.clickCard(page.BOAT); + browser.navigate().back(); + page.clickCard(page.POWERSPORTS); + browser.navigate().back(); + page.clickCard(page.TRAILER); + browser.navigate().back(); + page.clickCard(page.AUTO); + }); + + it('should display vehicle sub landing page with correct text and cards', () => { + const page = new SelectLoanVehicleSubPo(); + expect(page.getHeaderBar()).toBeDefined(); + const text = page.getTextByClassName('loan-header'); + expect(text).toEqual('Select a vehicle type'); + expect(page.isElementEnabled(page.LOAN)).toEqual(true); + expect(page.isElementEnabled(page.PRE)).toEqual(true); + page.clickCard(page.LOAN); + browser.navigate().back(); + page.clickCard(page.PRE); + browser.navigate().back(); + page.goPrevious(); + page.goPrevious(); + landingPage.clickCard(landingPage.HOME_EQUITY_CARD); + }); + + it('should display home equity landing page with correct text and cards', () => { + const page = new SelectLoanHomePo(); + expect(page.getHeaderBar()).toBeDefined(); + const text = page.getTextByClassName('loan-header'); + expect(text).toEqual('Select a home equity type'); + expect(page.isElementEnabled(page.HOME_EQUITY)).toEqual(true); + expect(page.isElementEnabled(page.MANU_HOME)).toEqual(true); + expect(page.isElementEnabled(page.MORT_REFI)).toEqual(true); + expect(page.isElementEnabled(page.HELOC)).toEqual(true); + page.clickCard(page.HOME_EQUITY); + browser.navigate().back(); + page.clickCard(page.MANU_HOME); + browser.navigate().back(); + page.clickCard(page.MORT_REFI); + browser.navigate().back(); + page.clickCard(page.HELOC); + browser.navigate().back(); + page.goPrevious(); + landingPage.clickCard(landingPage.PERSONAL_CARD); + }); + + it('should start personal loan ', () => { + const page = new SelectLoanPersonalPo(); + expect(page.getHeaderBar()).toBeDefined(); + browser.navigate().back(); + expect(landingPage.getHeaderBar()).toBeDefined(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/data-driven/test-input-command.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/data-driven/test-input-command.ts new file mode 100644 index 0000000..3941a9e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/data-driven/test-input-command.ts @@ -0,0 +1,12 @@ + +export enum TestingInputType { + TEXT, SELECT, CHECK, RADIO +} + +export interface TestInputCommand { + fieldId: string; + fieldValue: any; + fieldType: TestingInputType; +} + +export type TestInputSet = Array; diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/page-data-tools.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/page-data-tools.ts new file mode 100644 index 0000000..3a069a0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/page-data-tools.ts @@ -0,0 +1,22 @@ + +export class PageDataTools { + + public static readonly numbersList = '1234567890'; + public static readonly alphabetList = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + public static createRandomEmail(startLength = 10, endLength = 10): string { + const begin = this.createRandomString(startLength); + const end = this.createRandomString(endLength); + const email = [begin, '@', end, '.com']; + return email.join(''); + } + + public static createRandomString(stringLength: number) { + const characters = this.alphabetList; + let result = ''; + for (let j = stringLength; j > 0; --j) { + result += characters[Math.floor(Math.random() * characters.length)]; + } + return result; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/page-object-tools.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/page-object-tools.ts new file mode 100644 index 0000000..ae674d1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/page-object-tools.ts @@ -0,0 +1,87 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; +import { TestingInputType, TestInputCommand, TestInputSet } from './data-driven/test-input-command'; + +export class PageObjectTools { + + public static urlChanged(url): any { + return function () { + return browser.getCurrentUrl().then(function (actualUrl) { + return url !== actualUrl; + }); + }; + } + + public static fillFields(dataSet: TestInputSet, prefix?: string): void { + dataSet.forEach((command: TestInputCommand) => { + PageObjectTools.executeTestInputCommand(command, prefix); + }, this); + } + + static executeTestInputCommand(command: TestInputCommand, prefix?: string): any { + const fullId = prefix ? prefix + command.fieldId : command.fieldId; + const currentField = element(by.id(fullId)); + if (!currentField || !currentField.isDisplayed()) { + throw new Error('Couldn\'t execute command, field not found: ' + fullId); + } + switch (command.fieldType) { + default: + throw new Error('You must specify a field type for field Id: ' + fullId); + case TestingInputType.TEXT: + currentField.sendKeys(command.fieldValue); + break; + case TestingInputType.SELECT: + PageObjectTools.selectDropDownField(currentField, command.fieldValue); + break; + case TestingInputType.CHECK: + PageObjectTools.clickCheckBox(currentField); + break; + case TestingInputType.RADIO: + PageObjectTools.selectRadioButton(currentField, command.fieldValue); + break; + } + } + + public static findFieldsInPageByIdArray(fieldsIdSet: Array, prefix?: string) { + let foundField = null; + const usedPrefix = prefix || ''; + const allFields = []; + fieldsIdSet.forEach((fieldId, index) => { + foundField = element(by.id(usedPrefix + fieldId)); + if (foundField) { + allFields.push(foundField); + } + }); + return allFields; + } + + public static selectDropDownField(elementFinder: ElementFinder, optionNumber: number) { + const desiredOption = elementFinder.all(by.tagName('option')).get(optionNumber); + const EC = protractor.ExpectedConditions; + + browser.wait(EC.visibilityOf(desiredOption), 5000).then(() => { + desiredOption.click(); + }); + } + + public static selectRadioButton(elementFinder: ElementFinder, optionNumber: number) { + const desiredOption = elementFinder.all(by.tagName('label')).get(optionNumber); + const EC = protractor.ExpectedConditions; + + browser.wait(EC.visibilityOf(desiredOption), 5000).then(() => { + desiredOption.click(); + }); + } + + static clickCheckBox(checkbox: ElementFinder) { + browser.executeScript('arguments[0].click();', checkbox.getWebElement()); + } + + public static getElementsIdField(fieldId: string, prefix?: string): any { + const firstElement = element( by.id( prefix ? prefix + fieldId : fieldId)); + return firstElement.getAttribute( 'id' ); + } + + public static getFocusedElement(): any { + return browser.driver.switchTo().activeElement().getAttribute('id'); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/collateral-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/collateral-page.po.ts new file mode 100644 index 0000000..7031cea --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/collateral-page.po.ts @@ -0,0 +1,88 @@ +import { by, element, promise } from 'protractor'; +import { PageObjectTools } from '../page-object-tools'; +import { TestInputSet, TestingInputType } from '../data-driven/test-input-command'; + +export class CollateralPagePo { + + public readonly ESTIMATED_VALUE = '-inputEstimatedValue'; + public readonly PROPERTY_TYPE = '-inputPropertyType'; + public readonly HAS_FIRST_MORTGAGE = 'inputFirstMortgage'; + public readonly HAS_SECOND_MORTGAGE = 'inputSecondMortgage'; + public readonly PAYOFF_FIRST_MORTGAGE = 'payoffFirstMortgage'; + public readonly PAYOFF_SECOND_MORTGAGE = 'payoffSecondMortgage'; + public readonly IS_SAME_ADDRESS = 'isAddressSame'; + public readonly IS_BEING_IMPROVED = 'inputBeingImproved'; + public readonly IS_LISTED_FOR_SALE = 'inputListedForSale'; + // address information + public readonly ADDRESS_1 = '-inputAddress1'; + public readonly ADDRESS_2 = '-inputAddress2'; + public readonly CITY = '-inputCity'; + public readonly STATE = '-inputState'; + public readonly ZIP = '-inputZip'; + public readonly OCCUPANCY_TYPE = '-inputOccupancyType'; + + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly ALL_FIELDS = [this.ESTIMATED_VALUE, this.PROPERTY_TYPE, + this.IS_SAME_ADDRESS, this.IS_BEING_IMPROVED, this.IS_LISTED_FOR_SALE, this.NEXT_BUTTON]; + public readonly ADDRESS_FIELDS = [ this.OCCUPANCY_TYPE, this.ADDRESS_1, this.ADDRESS_2, this.CITY, this.STATE, this.ZIP]; + public readonly LIEN_POSITION_FIELDS = [ this.HAS_FIRST_MORTGAGE, this.HAS_SECOND_MORTGAGE, this.PAYOFF_FIRST_MORTGAGE,this.PAYOFF_SECOND_MORTGAGE]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.IS_SAME_ADDRESS, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.IS_BEING_IMPROVED, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.IS_LISTED_FOR_SALE, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.ESTIMATED_VALUE, fieldValue: '12345', fieldType: TestingInputType.TEXT }, + { fieldId: this.PROPERTY_TYPE, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.HAS_FIRST_MORTGAGE, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.HAS_SECOND_MORTGAGE, fieldValue: 0, fieldType: TestingInputType.RADIO } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + { fieldId: this.IS_SAME_ADDRESS, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.ADDRESS_1, fieldValue: '1000 South 8th Pl.', fieldType: TestingInputType.TEXT }, + { fieldId: this.CITY, fieldValue: 'Codeville', fieldType: TestingInputType.TEXT }, + { fieldId: this.STATE, fieldValue: 40, fieldType: TestingInputType.SELECT }, + { fieldId: this.ZIP, fieldValue: 53207, fieldType: TestingInputType.TEXT }, + { fieldId: this.IS_BEING_IMPROVED, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.IS_LISTED_FOR_SALE, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.OCCUPANCY_TYPE, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.ESTIMATED_VALUE, fieldValue: '12345', fieldType: TestingInputType.TEXT }, + { fieldId: this.PROPERTY_TYPE, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.HAS_FIRST_MORTGAGE, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.HAS_SECOND_MORTGAGE, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.PAYOFF_FIRST_MORTGAGE, fieldValue: 0, fieldType: TestingInputType.CHECK }, + { fieldId: this.PAYOFF_SECOND_MORTGAGE, fieldValue: 0, fieldType: TestingInputType.CHECK }, + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA); + } + + + public getFocusedElement() { + return PageObjectTools.getFocusedElement(); + } + + public getFirstElement() { + // it focuses on the yes/no radio, not the overall input + return PageObjectTools.getElementsIdField('isSameYes'); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/contact-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/contact-page.po.ts new file mode 100644 index 0000000..0547162 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/contact-page.po.ts @@ -0,0 +1,125 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; +import { PageObjectTools } from '../page-object-tools'; +import { PageDataTools } from '../page-data-tools'; +import { TestInputSet, TestingInputType } from '../data-driven/test-input-command'; + +export class ContactPagePo { + + public readonly FIRST_NAME = 'inputFirstName'; + public readonly MIDDLE_INITIAL = 'inputMiddleInitial'; + public readonly LAST_NAME = 'inputLastName'; + public readonly DOB = 'inputDOB'; + public readonly SUFFIX = 'inputSuffix'; + public readonly SSN = 'inputSSN'; + public readonly PRIMARY_PHONE = 'inputPrimaryPhoneNumber'; + public readonly PRIMARY_PHONE_TYPE = 'inputPrimaryPhoneType'; + public readonly SECONDARY_PHONE = 'inputSecondaryPhoneNumber'; + public readonly SECONDARY_PHONE_TYPE = 'inputSecondaryPhoneType'; + public readonly EMAIL = 'inputEmail'; + public readonly MARITAL_STATUS = 'inputMaritalStatus'; + public readonly CO_APPLICANT_SPOUSE = 'inputCoApplicantSpouse'; + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly ALL_FIELDS = [ + this.FIRST_NAME, this.MIDDLE_INITIAL, this.LAST_NAME, this.DOB, this.SUFFIX, this.SSN, this.PRIMARY_PHONE, + this.PRIMARY_PHONE_TYPE, this.SECONDARY_PHONE, this.SECONDARY_PHONE_TYPE, this.EMAIL, this.NEXT_BUTTON]; + public readonly COAPP_FIELDS = [ ...this.ALL_FIELDS, this.MARITAL_STATUS, this.CO_APPLICANT_SPOUSE]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.FIRST_NAME, fieldValue: 'John', fieldType: TestingInputType.TEXT }, + { fieldId: this.MIDDLE_INITIAL, fieldValue: 'A', fieldType: TestingInputType.TEXT }, + { fieldId: this.LAST_NAME, fieldValue: 'DOE', fieldType: TestingInputType.TEXT }, + { fieldId: this.DOB, fieldValue: '01011999', fieldType: TestingInputType.TEXT }, + { fieldId: this.SSN, fieldValue: '111223333', fieldType: TestingInputType.TEXT }, + { fieldId: this.PRIMARY_PHONE, fieldValue: '8475551234', fieldType: TestingInputType.TEXT }, + { fieldId: this.EMAIL, fieldValue: PageDataTools.createRandomEmail(), fieldType: TestingInputType.TEXT }, + { fieldId: this.MARITAL_STATUS, fieldValue: 1, fieldType: TestingInputType.RADIO } + ]; + + public readonly MINIMAL_DATA_NO_MARITAL: TestInputSet = [ + { fieldId: this.FIRST_NAME, fieldValue: 'John', fieldType: TestingInputType.TEXT }, + { fieldId: this.MIDDLE_INITIAL, fieldValue: 'A', fieldType: TestingInputType.TEXT }, + { fieldId: this.LAST_NAME, fieldValue: 'DOE', fieldType: TestingInputType.TEXT }, + { fieldId: this.DOB, fieldValue: '01011999', fieldType: TestingInputType.TEXT }, + { fieldId: this.SSN, fieldValue: '111223333', fieldType: TestingInputType.TEXT }, + { fieldId: this.PRIMARY_PHONE, fieldValue: '8475551234', fieldType: TestingInputType.TEXT }, + { fieldId: this.EMAIL, fieldValue: PageDataTools.createRandomEmail(), fieldType: TestingInputType.TEXT } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + ...this.MINIMAL_DATA, + { fieldId: this.SUFFIX, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.PRIMARY_PHONE_TYPE, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.SECONDARY_PHONE, fieldValue: '8475561234', fieldType: TestingInputType.TEXT }, + { fieldId: this.SECONDARY_PHONE_TYPE, fieldValue: 0, fieldType: TestingInputType.RADIO } + ]; + + public readonly MAXIMUM_COAPP_DATA: TestInputSet = [ + { fieldId: this.FIRST_NAME, fieldValue: 'Jane', fieldType: TestingInputType.TEXT }, + { fieldId: this.MIDDLE_INITIAL, fieldValue: 'B', fieldType: TestingInputType.TEXT }, + { fieldId: this.LAST_NAME, fieldValue: 'DOE', fieldType: TestingInputType.TEXT }, + { fieldId: this.DOB, fieldValue: '02021998', fieldType: TestingInputType.TEXT }, + { fieldId: this.SUFFIX, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.SSN, fieldValue: '333223333', fieldType: TestingInputType.TEXT }, + { fieldId: this.PRIMARY_PHONE, fieldValue: '8475554321', fieldType: TestingInputType.TEXT }, + { fieldId: this.EMAIL, fieldValue: PageDataTools.createRandomEmail(), fieldType: TestingInputType.TEXT }, + { fieldId: this.PRIMARY_PHONE_TYPE, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.SECONDARY_PHONE, fieldValue: '8475564321', fieldType: TestingInputType.TEXT }, + { fieldId: this.SECONDARY_PHONE_TYPE, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.MARITAL_STATUS, fieldValue: 0, fieldType: TestingInputType.RADIO } + ]; + + public readonly MAXIMUM_COAPP_DATA_PRIMARY: TestInputSet = [ + ...this.MAXIMUM_COAPP_DATA, + { fieldId: this.CO_APPLICANT_SPOUSE, fieldValue: 0, fieldType: TestingInputType.RADIO } + ]; + + public readonly MAXIMUM_PRIMARY_NOT_MARRIED_TO_COAPP: TestInputSet = [ + ...this.MAXIMUM_COAPP_DATA, + { fieldId: this.CO_APPLICANT_SPOUSE, fieldValue: 1, fieldType: TestingInputType.RADIO } + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA); + } + + public fillFieldsMinimalNoMarital(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA_NO_MARITAL); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA); + } + + public fillFieldsMaximumCoApplicant(): void { + PageObjectTools.fillFields(this.MAXIMUM_COAPP_DATA_PRIMARY); + } + + public fillFieldsMaximumCoApplicantNonPrimary(): void { + PageObjectTools.fillFields(this.MAXIMUM_COAPP_DATA); + } + + public fillFieldsPrimaryMarriedButNotMarriedToCoApplicant(): void { + PageObjectTools.fillFields(this.MAXIMUM_PRIMARY_NOT_MARRIED_TO_COAPP); + } + + public getFocusedElement() { + return PageObjectTools.getFocusedElement(); + } + + public getFirstElement(prefix?: string) { + return PageObjectTools.getElementsIdField(this.FIRST_NAME, prefix); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/employment-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/employment-page.po.ts new file mode 100644 index 0000000..1fd5c08 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/employment-page.po.ts @@ -0,0 +1,137 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; +import { PageObjectTools } from '../page-object-tools'; +import { TestingInputType, TestInputSet } from '../data-driven/test-input-command'; + +export class EmploymentPagePo { + + public readonly DEFAULT_PREFIX = ''; + public readonly PRIOR0_PREFIX = 'priorEmployer0-'; + + public readonly EMPLOYMENT_INDICATED = 'hasEmploymentIncome0'; + public readonly OTHER_INCOME_INDICATED = 'hasOtherIncome0'; + public readonly IS_PAYING_CHILD_SUPPORT = 'isPayingChildSupport'; + + public readonly EMPLOYER_NAME = 'inputEmployerName'; + public readonly JOB_TITLE = 'inputJobTitle'; + public readonly SALARY = 'inputSalary'; + public readonly OTHER_INCOME = 'inputOtherAnnualIncome'; + public readonly OTHER_INCOME_SOURCE = 'inputOtherAnnualIncomeSource'; + public readonly WORK_PHONE = 'inputWorkPhoneNumber'; + public readonly EMPLOYMENT_TYPE = 'inputEmploymentType'; + public readonly SCHEDULE = 'inputSchedule'; + public readonly EMPLOYMENT_LENGTH_YEARS = 'inputEmploymentLengthYears'; + public readonly EMPLOYMENT_LENGTH_MONTHS = 'inputEmploymentLengthMonths'; + public readonly CHILD_SUPPORT_AMOUNT = 'inputChildSupportAmount'; + public readonly WEEKLY_HOURS = 'inputWeeklyHours'; + public readonly NEXT_BUTTON = 'buttonNext'; + + public readonly STARTING_FIELDS = [this.EMPLOYMENT_INDICATED, this.OTHER_INCOME_INDICATED, this.IS_PAYING_CHILD_SUPPORT]; + public readonly EMPLOYMENT_FIELDS = [ + this.EMPLOYER_NAME, this.JOB_TITLE, this.SALARY, this.WORK_PHONE, this.EMPLOYMENT_TYPE, this.SCHEDULE, + this.EMPLOYMENT_LENGTH_YEARS, this.EMPLOYMENT_LENGTH_MONTHS]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.EMPLOYER_NAME, fieldValue: 'Acme Packaging', fieldType: TestingInputType.TEXT }, + { fieldId: this.JOB_TITLE, fieldValue: 'Acme Packer', fieldType: TestingInputType.TEXT }, + { fieldId: this.SALARY, fieldValue: 30000, fieldType: TestingInputType.TEXT }, + { fieldId: this.EMPLOYMENT_TYPE, fieldValue: 1, fieldType: TestingInputType.SELECT }, + { fieldId: this.SCHEDULE, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.EMPLOYMENT_LENGTH_YEARS, fieldValue: 3, fieldType: TestingInputType.SELECT }, + { fieldId: this.EMPLOYMENT_LENGTH_MONTHS, fieldValue: 2, fieldType: TestingInputType.SELECT } + ]; + + public readonly SECOND_INCOME_DATA: TestInputSet = [ + { fieldId: this.EMPLOYER_NAME, fieldValue: 'Acme Delivery', fieldType: TestingInputType.TEXT }, + { fieldId: this.JOB_TITLE, fieldValue: 'Delivery Driver', fieldType: TestingInputType.TEXT }, + { fieldId: this.SALARY, fieldValue: 15, fieldType: TestingInputType.TEXT }, + { fieldId: this.EMPLOYMENT_TYPE, fieldValue: 1, fieldType: TestingInputType.SELECT }, + { fieldId: this.SCHEDULE, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.EMPLOYMENT_LENGTH_YEARS, fieldValue: 3, fieldType: TestingInputType.SELECT }, + { fieldId: this.EMPLOYMENT_LENGTH_MONTHS, fieldValue: 2, fieldType: TestingInputType.SELECT } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + ...this.MINIMAL_DATA, + { fieldId: this.WORK_PHONE, fieldValue: 4148765309, fieldType: TestingInputType.TEXT }, + ]; + + public readonly OTHER_INCOME_DATA: TestInputSet = [ + { fieldId: this.OTHER_INCOME, fieldValue: 20000, fieldType: TestingInputType.TEXT }, + { fieldId: this.OTHER_INCOME_SOURCE, fieldValue: 'Stuff1', fieldType: TestingInputType.TEXT } + ]; + + public readonly CHILD_SUPPORT_DATA: TestInputSet = [ + { fieldId: this.CHILD_SUPPORT_AMOUNT, fieldValue: 200, fieldType: TestingInputType.TEXT } + ]; + + // Page hides everything by default + public showEmploymentFields(): void { + const showEmployment = element(by.id(this.EMPLOYMENT_INDICATED)); + PageObjectTools.clickCheckBox(showEmployment); + } + + public showOtherIncomeFields(): void { + const showOtherIncomeOne = element(by.id(this.OTHER_INCOME_INDICATED)); + PageObjectTools.clickCheckBox(showOtherIncomeOne); + } + + public toggleOtherIncomeGroupOne(): void { + const showOtherIncomeOne = element(by.id(this.OTHER_INCOME_INDICATED)); + PageObjectTools.clickCheckBox(showOtherIncomeOne); + } + + public clearOtherIncomeGroupOne(): void { + const clearOtherIncomeOneSource = element(by.id(this.OTHER_INCOME_SOURCE)); + const clearOtherIncomeOne = element(by.id(this.OTHER_INCOME)); + clearOtherIncomeOneSource.clear(); + clearOtherIncomeOne.clear(); + } + + public showChildSupportFields(): void { + const showChildSupport = element(by.id(this.IS_PAYING_CHILD_SUPPORT)); + PageObjectTools.clickCheckBox(showChildSupport); + } + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA, this.DEFAULT_PREFIX); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA, this.DEFAULT_PREFIX); + } + + public fillChildSupportFields(): void { + PageObjectTools.fillFields(this.CHILD_SUPPORT_DATA); + } + + public fillOtherIncomeFields(): void { + PageObjectTools.fillFields(this.OTHER_INCOME_DATA); + } + + /** + * Please note, this may need to be adjusted to match the current required employment length. + * As of 09/14/18, this isn't called... + */ + public markSingleIncomeNotEnough(): void { + PageObjectTools.executeTestInputCommand( + { fieldId: this.EMPLOYMENT_LENGTH_YEARS, fieldValue: 1, fieldType: TestingInputType.SELECT }, this.DEFAULT_PREFIX); + } + + public fillSecondIncome(): void { + PageObjectTools.fillFields(this.SECOND_INCOME_DATA, this.PRIOR0_PREFIX); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/hmda-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/hmda-page.po.ts new file mode 100644 index 0000000..85f4b69 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/hmda-page.po.ts @@ -0,0 +1,122 @@ +import { by, element, promise } from 'protractor'; +import { PageObjectTools } from '../page-object-tools'; +import { TestInputSet, TestingInputType } from '../data-driven/test-input-command'; + +export class HmdaPagePo { + + public readonly DEFAULT_PREFIX = 'applicant-'; + // race information + public readonly AMERICAN_INDIAN_ALASKA_NATIVE = 'inputAmericanIndianAlaskaNative'; + public readonly AMERICAN_INDIAN_ALASKA_NATIVE_TEXT = 'inputAmericanIndianAlaskaNativeText'; + public readonly BLACK_OR_AFRICAN_AMERICAN = 'inputBlackOrAfricanAmerican'; + public readonly ASIAN = 'inputAsian'; + public readonly ASIAN_INDIAN = 'inputAsianIndian'; + public readonly CHINESE = 'inputChinese'; + public readonly FILIPINO = 'inputFilipino'; + public readonly JAPANESE = 'inputJapanese'; + public readonly KOREAN = 'inputKorean'; + public readonly VIETNAMESE = 'inputVietnamese'; + public readonly OTHER_ASIAN = 'inputOtherAsian'; + public readonly OTHER_ASIAN_TEXT = 'inputOtherAsianText'; + public readonly HAWAIIAN = 'inputHawaiian'; + public readonly NATIVE_HAWAIIAN = 'inputNativeHawaiian'; + public readonly GUAMANIAN_CHAMORRO = 'inputNativeGuamanianChamorro'; + public readonly SAMOAN = 'inputNativeSamoan'; + public readonly OTHER_PACIFIC_ISLANDER = 'inputOtherPacificIslander'; + public readonly OTHER_PACIFIC_ISLANDER_TEXT = 'inputOtherPacificIslanderText'; + public readonly WHITE = 'inputWhite'; + public readonly RACE_DID_NOT_PROVIDE = 'inputRaceDidNotProvide'; + // ethnicity information + public readonly HISPANIC_OR_LATINO = 'inputHispanicOrLatino'; + public readonly MEXICAN = 'inputMexican'; + public readonly PUERTO_RICAN = 'inputPuertoRican'; + public readonly CUBAN = 'inputCuban'; + public readonly OTHER_HISPANIC_LATINO = 'inputOtherHispanicLatino'; + public readonly OTHER_HISPANIC_LATINO_TEXT = 'inputOtherHispanicLatinoText'; + public readonly NOT_HISPANIC_OR_LATINO = 'inputNotHispanicOrLatino'; + public readonly ETHNICITY_DID_NOT_PROVIDE = 'inputEthnicityDidNotProvide'; + // gender information + public readonly FEMALE = 'inputFemale'; + public readonly MALE = 'inputMale'; + public readonly GENDER_DID_NOT_PROVIDE = 'inputGenderDidNotProvide'; + + public readonly NEXT_BUTTON = 'buttonNext'; + + public readonly GENDER_FIELDS = [ + this.MALE, this.FEMALE, this.GENDER_DID_NOT_PROVIDE + ]; + + public readonly RACE_FIELDS = [ + this.AMERICAN_INDIAN_ALASKA_NATIVE, this.AMERICAN_INDIAN_ALASKA_NATIVE_TEXT, this.BLACK_OR_AFRICAN_AMERICAN, this.ASIAN, + this.ASIAN_INDIAN, this.CHINESE, this.FILIPINO, this.JAPANESE, this.KOREAN, this.VIETNAMESE, this.OTHER_ASIAN, this.OTHER_ASIAN_TEXT, + this.HAWAIIAN, this.NATIVE_HAWAIIAN, this.GUAMANIAN_CHAMORRO, this.SAMOAN, this.OTHER_PACIFIC_ISLANDER, + this.WHITE, this.RACE_DID_NOT_PROVIDE + ]; + + public readonly ETHNICITY_FIELDS = [ + this.HISPANIC_OR_LATINO, this.MEXICAN, this.PUERTO_RICAN, this.CUBAN, this.OTHER_HISPANIC_LATINO, + this.OTHER_HISPANIC_LATINO_TEXT, this.NOT_HISPANIC_OR_LATINO, this.ETHNICITY_DID_NOT_PROVIDE, + ]; + + public readonly ALL_FIELDS = [ + ...this.RACE_FIELDS, ...this.GENDER_FIELDS, ...this.ETHNICITY_FIELDS, this.NEXT_BUTTON + ]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.RACE_DID_NOT_PROVIDE, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.ETHNICITY_DID_NOT_PROVIDE, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.GENDER_DID_NOT_PROVIDE, fieldValue: 1, fieldType: TestingInputType.CHECK } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + ...this.MINIMAL_DATA, + { fieldId: this.AMERICAN_INDIAN_ALASKA_NATIVE, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.AMERICAN_INDIAN_ALASKA_NATIVE_TEXT, fieldValue: 'Enrolled principal or tribe', fieldType: TestingInputType.TEXT }, + { fieldId: this.BLACK_OR_AFRICAN_AMERICAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.ASIAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.ASIAN_INDIAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.CHINESE, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.FILIPINO, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.JAPANESE, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.KOREAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.VIETNAMESE, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.OTHER_ASIAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.OTHER_ASIAN_TEXT, fieldValue: 'Hmong, Laotian, Thai,Pakistani, Cambodian', fieldType: TestingInputType.TEXT }, + { fieldId: this.HAWAIIAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.NATIVE_HAWAIIAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.GUAMANIAN_CHAMORRO, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.SAMOAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.OTHER_PACIFIC_ISLANDER, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.OTHER_PACIFIC_ISLANDER_TEXT, fieldValue: 'Fijian, Tongan', fieldType: TestingInputType.TEXT }, + { fieldId: this.WHITE, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.HISPANIC_OR_LATINO, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.MEXICAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.PUERTO_RICAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.CUBAN, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.OTHER_HISPANIC_LATINO, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.OTHER_HISPANIC_LATINO_TEXT, fieldValue: 'Argentinean,Colombian, Dominican', fieldType: TestingInputType.TEXT }, + { fieldId: this.NOT_HISPANIC_OR_LATINO, fieldValue: 1, fieldType: TestingInputType.CHECK }, + { fieldId: this.MALE, fieldValue: 1, fieldType: TestingInputType.CHECK }, + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA , this.DEFAULT_PREFIX); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA , this.DEFAULT_PREFIX); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/residence-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/residence-page.po.ts new file mode 100644 index 0000000..57850ff --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/residence-page.po.ts @@ -0,0 +1,143 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; +import { PageObjectTools } from '../page-object-tools'; +import { TestingInputType, TestInputSet } from '../data-driven/test-input-command'; + +export class ResidencePagePo { + + public readonly DEFAULT_PREFIX = 'addressInfo-'; + public readonly MAILING_PREFIX = 'mailingInfo-'; + public readonly PRIOR_PREFIX = '0-'; + public readonly COAPP_PREFIX = '-'; + + public readonly ADDRESS_LINE_1 = 'inputAddress1'; + public readonly ADDRESS_LINE_2 = 'inputAddress2'; + public readonly CITY = 'inputCity'; + public readonly STATE = 'inputState'; + public readonly ZIP = 'inputZip'; + public readonly RESIDENCE_STATUS = 'inputResidenceStatus'; + public readonly RESIDENCE_MONTHLY_PAYMENT = 'inputResidenceMonthlyPayment'; + public readonly RESIDENCE_TIME_YEARS = 'inputResidenceTimeYears'; + public readonly RESIDENCE_TIME_MONTHS = 'inputResidenceTimeMonths'; + public readonly MAILING_SAME = 'isMailingSameYes'; + public readonly IS_ADDRESS_SAME = 'isAddressSame'; + public readonly NEXT_BUTTON = 'buttonNext'; + + public readonly ALL_FIELDS = [ + this.ADDRESS_LINE_1, this.ADDRESS_LINE_2, this.CITY, this.STATE, this.ZIP, this.RESIDENCE_STATUS, + this.RESIDENCE_MONTHLY_PAYMENT, this.RESIDENCE_TIME_YEARS, this.RESIDENCE_TIME_MONTHS, this.MAILING_SAME + ]; + public readonly MAILING_FIELDS = [ + this.ADDRESS_LINE_1, this.ADDRESS_LINE_2, this.CITY, this.STATE, this.ZIP + ]; + public readonly COAPP_FIELDS = [ + ...this.ALL_FIELDS + ]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.ADDRESS_LINE_1, fieldValue: '999 South 8th Pl.', fieldType: TestingInputType.TEXT }, + { fieldId: this.CITY, fieldValue: 'Codeville', fieldType: TestingInputType.TEXT }, + { fieldId: this.STATE, fieldValue: 40, fieldType: TestingInputType.SELECT }, + { fieldId: this.ZIP, fieldValue: 53207, fieldType: TestingInputType.TEXT }, + { fieldId: this.RESIDENCE_MONTHLY_PAYMENT, fieldValue: 1500, fieldType: TestingInputType.TEXT }, + { fieldId: this.RESIDENCE_STATUS, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.RESIDENCE_TIME_YEARS, fieldValue: 4, fieldType: TestingInputType.SELECT }, + { fieldId: this.RESIDENCE_TIME_MONTHS, fieldValue: 4, fieldType: TestingInputType.SELECT } + ]; + + public readonly MINIMAL_DATA_IN_WI: TestInputSet = [ + { fieldId: this.ADDRESS_LINE_1, fieldValue: '999 South 8th Pl.', fieldType: TestingInputType.TEXT }, + { fieldId: this.CITY, fieldValue: 'Codeville', fieldType: TestingInputType.TEXT }, + { fieldId: this.ZIP, fieldValue: 53207, fieldType: TestingInputType.TEXT }, + { fieldId: this.RESIDENCE_MONTHLY_PAYMENT, fieldValue: 1500, fieldType: TestingInputType.TEXT }, + { fieldId: this.RESIDENCE_STATUS, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.RESIDENCE_TIME_YEARS, fieldValue: 4, fieldType: TestingInputType.SELECT }, + { fieldId: this.RESIDENCE_TIME_MONTHS, fieldValue: 4, fieldType: TestingInputType.SELECT } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + { fieldId: this.ADDRESS_LINE_1, fieldValue: '1000 South 8th Pl.', fieldType: TestingInputType.TEXT }, + { fieldId: this.CITY, fieldValue: 'Codeville', fieldType: TestingInputType.TEXT }, + { fieldId: this.STATE, fieldValue: 40, fieldType: TestingInputType.SELECT }, + { fieldId: this.ZIP, fieldValue: 53207, fieldType: TestingInputType.TEXT }, + { fieldId: this.RESIDENCE_MONTHLY_PAYMENT, fieldValue: 1500, fieldType: TestingInputType.TEXT }, + { fieldId: this.RESIDENCE_STATUS, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.RESIDENCE_TIME_YEARS, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.RESIDENCE_TIME_MONTHS, fieldValue: 2, fieldType: TestingInputType.SELECT } + ]; + + public readonly MAILING_DATA: TestInputSet = [ + { fieldId: this.ADDRESS_LINE_1, fieldValue: '111 North 7th St.', fieldType: TestingInputType.TEXT }, + { fieldId: this.CITY, fieldValue: 'Codeville', fieldType: TestingInputType.TEXT }, + { fieldId: this.STATE, fieldValue: 40, fieldType: TestingInputType.SELECT }, + { fieldId: this.ZIP, fieldValue: 53215, fieldType: TestingInputType.TEXT } + ]; + + public readonly PRIOR_RESIDENCE_DATA: TestInputSet = [ + { fieldId: this.ADDRESS_LINE_1, fieldValue: '1000 South 8th Pl.', fieldType: TestingInputType.TEXT }, + { fieldId: this.CITY, fieldValue: 'Codeville', fieldType: TestingInputType.TEXT }, + { fieldId: this.STATE, fieldValue: 40, fieldType: TestingInputType.SELECT }, + { fieldId: this.ZIP, fieldValue: 53207, fieldType: TestingInputType.TEXT }, + { fieldId: this.RESIDENCE_STATUS, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.RESIDENCE_TIME_YEARS, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.RESIDENCE_TIME_MONTHS, fieldValue: 2, fieldType: TestingInputType.SELECT } + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA, this.DEFAULT_PREFIX); + } + + public showMailingAddress(): void { + const mailingIsSameCheck = element(by.id(this.DEFAULT_PREFIX + this.MAILING_SAME)); + PageObjectTools.clickCheckBox(mailingIsSameCheck); + } + + public showSecondAddress(): void { + PageObjectTools.executeTestInputCommand( + { fieldId: this.RESIDENCE_TIME_YEARS, fieldValue: 2, fieldType: TestingInputType.SELECT }, this.DEFAULT_PREFIX); + } + + public markAddressNotSame(): void { + PageObjectTools.executeTestInputCommand( + { fieldId: this.IS_ADDRESS_SAME, fieldValue: 1, fieldType: TestingInputType.RADIO }); + } + + public markAddressSame(): void { + PageObjectTools.executeTestInputCommand( + { fieldId: this.IS_ADDRESS_SAME, fieldValue: 0, fieldType: TestingInputType.RADIO }); + } + + public fillFieldsCoApplicant(): void { + PageObjectTools.fillFields(this.MAILING_DATA, this.COAPP_PREFIX); + } + + public fillAdditionalFieldsMaximum(): void { + PageObjectTools.fillFields(this.PRIOR_RESIDENCE_DATA, this.PRIOR_PREFIX); + PageObjectTools.fillFields(this.MAILING_DATA, this.MAILING_PREFIX); + } + + public fillFieldsMinimalLivingInWI(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA_IN_WI, this.DEFAULT_PREFIX); + } + + public getFocusedElement() { + return PageObjectTools.getFocusedElement(); + } + + public getFirstElement(prefix?: string) { + return PageObjectTools.getElementsIdField(this.ADDRESS_LINE_1, prefix); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/spouse-type-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/spouse-type-page.po.ts new file mode 100644 index 0000000..248f107 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/spouse-type-page.po.ts @@ -0,0 +1,41 @@ +import { by, element, promise } from 'protractor'; +import { PageObjectTools } from '../page-object-tools'; +import { TestingInputType, TestInputSet } from '../data-driven/test-input-command'; + +export class SpouseTypePagePo { + + public readonly IS_FULL_INFO_SPOUSE = 'inputIsFullInfoSpouse'; + public readonly NEXT_BUTTON = 'buttonNext'; + + public readonly STARTING_FIELDS = [this.IS_FULL_INFO_SPOUSE]; + + public readonly FULL_INFO_SPOUSE_DATA: TestInputSet = [ + { fieldId: this.IS_FULL_INFO_SPOUSE, fieldValue: 0, fieldType: TestingInputType.RADIO } + ]; + + public readonly TATTLE_SPOUSE_DATA: TestInputSet = [ + { fieldId: this.IS_FULL_INFO_SPOUSE, fieldValue: 1, fieldType: TestingInputType.RADIO } + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsFullInfoSpouse(): void { + PageObjectTools.fillFields(this.FULL_INFO_SPOUSE_DATA); + } + + public fillFieldsTattleSpouse(): void { + PageObjectTools.fillFields(this.TATTLE_SPOUSE_DATA); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/summary-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/summary-page.po.ts new file mode 100644 index 0000000..5e82ee2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/summary-page.po.ts @@ -0,0 +1,34 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; +import { PageObjectTools } from '../page-object-tools'; +import { PageDataTools } from '../page-data-tools'; +import { TestInputSet, TestingInputType } from '../data-driven/test-input-command'; + +export class SummaryPagePo { + + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly NOTE_FIELD = 'inputNote'; + public readonly PRIMARY_CONTACT = 'inputContact'; + public readonly PROMO_CODE = 'inputPromoCode'; + public readonly ALL_FIELDS = [this.NOTE_FIELD, this.NEXT_BUTTON]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.PRIMARY_CONTACT, fieldValue: 1, fieldType: TestingInputType.SELECT }, + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/tattle-spouse-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/tattle-spouse-page.po.ts new file mode 100644 index 0000000..f43bfa0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/tattle-spouse-page.po.ts @@ -0,0 +1,75 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; +import { PageObjectTools } from '../page-object-tools'; +import { TestingInputType, TestInputSet } from '../data-driven/test-input-command'; + +export class TattleSpousePagePo { + + public readonly FIRST_NAME = 'inputFirstName'; + public readonly MIDDLE_INITIAL = 'inputMiddleInitial'; + public readonly LAST_NAME = 'inputLastName'; + + public readonly ADDRESS_LINE_1 = 'inputAddress1'; + public readonly ADDRESS_LINE_2 = 'inputAddress2'; + public readonly CITY = 'inputCity'; + public readonly STATE = 'inputState'; + public readonly ZIP = 'inputZip'; + + public readonly IS_ADDRESS_SAME = 'isAddressSame'; + public readonly NEXT_BUTTON = 'buttonNext'; + + public readonly ALL_FIELDS = [ + this.ADDRESS_LINE_1, this.ADDRESS_LINE_2, this.CITY, this.STATE, this.ZIP, this.IS_ADDRESS_SAME, + this.FIRST_NAME, this.MIDDLE_INITIAL, this.LAST_NAME + ]; + public readonly MAILING_FIELDS = [ + this.ADDRESS_LINE_1, this.ADDRESS_LINE_2, this.CITY, this.STATE, this.ZIP + ]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.FIRST_NAME, fieldValue: 'Bob', fieldType: TestingInputType.TEXT }, + { fieldId: this.MIDDLE_INITIAL, fieldValue: 'B', fieldType: TestingInputType.TEXT }, + { fieldId: this.LAST_NAME, fieldValue: 'Uecker', fieldType: TestingInputType.TEXT }, + { fieldId: this.IS_ADDRESS_SAME, fieldValue: 0, fieldType: TestingInputType.RADIO } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + { fieldId: this.FIRST_NAME, fieldValue: 'Bob', fieldType: TestingInputType.TEXT }, + { fieldId: this.MIDDLE_INITIAL, fieldValue: 'B', fieldType: TestingInputType.TEXT }, + { fieldId: this.LAST_NAME, fieldValue: 'Uecker', fieldType: TestingInputType.TEXT }, + { fieldId: this.IS_ADDRESS_SAME, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.ADDRESS_LINE_1, fieldValue: '1000 South 8th Pl.', fieldType: TestingInputType.TEXT }, + { fieldId: this.CITY, fieldValue: 'Codeville', fieldType: TestingInputType.TEXT }, + { fieldId: this.STATE, fieldValue: 40, fieldType: TestingInputType.SELECT }, + { fieldId: this.ZIP, fieldValue: 53207, fieldType: TestingInputType.TEXT } + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA); + } + + public getFocusedElement() { + return PageObjectTools.getFocusedElement(); + } + + public getFirstElement(prefix?: string) { + return PageObjectTools.getElementsIdField(this.ADDRESS_LINE_1, prefix); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/vehicle-collateral-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/vehicle-collateral-page.po.ts new file mode 100644 index 0000000..8224ac1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/vehicle-collateral-page.po.ts @@ -0,0 +1,93 @@ +import { by, element, promise } from 'protractor'; +import { PageObjectTools } from '../page-object-tools'; +import { TestInputSet, TestingInputType } from '../data-driven/test-input-command'; + +export class VehicleCollateralPagePo { + + // text input fields + public readonly VALUE = 'inputValue'; + public readonly YEAR = 'inputYear'; + public readonly MAKE = 'inputMake'; + public readonly MODEL = 'inputModel'; + public readonly MILEAGE = 'inputMileage'; + // drop down menus + public readonly TERM = 'inputTerm'; + public readonly CONDITION = 'inputCondition'; + public readonly LOAN_PURPOSE = 'inputLoanPurpose'; + // radio/pill buttons + public readonly TYPE = 'inputType'; + public readonly SELLER = 'inputSeller'; + + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly ALL_FIELDS = [this.VALUE, this.CONDITION, this.YEAR, + this.MAKE, this.MODEL, this.MILEAGE, this.TERM, this.LOAN_PURPOSE, this.TYPE, this.SELLER, this.NEXT_BUTTON]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.VALUE, fieldValue: '12345', fieldType: TestingInputType.TEXT }, + { fieldId: this.TYPE, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.CONDITION, fieldValue: 1, fieldType: TestingInputType.SELECT }, + { fieldId: this.YEAR, fieldValue: '2018', fieldType: TestingInputType.TEXT }, + { fieldId: this.MAKE, fieldValue: 'Subaru', fieldType: TestingInputType.TEXT }, + { fieldId: this.MODEL, fieldValue: 'Outback', fieldType: TestingInputType.TEXT }, + { fieldId: this.MILEAGE, fieldValue: '17320', fieldType: TestingInputType.TEXT }, + { fieldId: this.TERM, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.LOAN_PURPOSE, fieldValue: 1, fieldType: TestingInputType.SELECT } + ]; + + public readonly VEHICLE_SELLER_INFO: TestInputSet = [ + { fieldId: this.SELLER, fieldValue: 1, fieldType: TestingInputType.RADIO } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + { fieldId: this.VALUE, fieldValue: '12345', fieldType: TestingInputType.TEXT }, + { fieldId: this.TYPE, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.CONDITION, fieldValue: 1, fieldType: TestingInputType.SELECT }, + { fieldId: this.YEAR, fieldValue: '2018', fieldType: TestingInputType.TEXT }, + { fieldId: this.MAKE, fieldValue: 'Subaru', fieldType: TestingInputType.TEXT }, + { fieldId: this.MODEL, fieldValue: 'Outback', fieldType: TestingInputType.TEXT }, + { fieldId: this.MILEAGE, fieldValue: '17320', fieldType: TestingInputType.TEXT }, + { fieldId: this.TERM, fieldValue: 2, fieldType: TestingInputType.SELECT }, + { fieldId: this.LOAN_PURPOSE, fieldValue: 2, fieldType: TestingInputType.SELECT }, + ...this.VEHICLE_SELLER_INFO + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public canClickDropdown(): promise.Promise { + const dropdown = element(by.id(this.TERM)); + + if (dropdown) { + return dropdown.isEnabled(); + } + throw new Error('Couldn\'t click the dropbox'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA); + } + + + public getFocusedElement() { + return PageObjectTools.getFocusedElement(); + } + + public getFirstElement() { + return PageObjectTools.getElementsIdField(this.VALUE); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/wizard-page.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/wizard-page.po.ts new file mode 100644 index 0000000..2ad51de --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/pages/wizard-page.po.ts @@ -0,0 +1,35 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; + +export class WizardPagePo { + + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly PREVIOUS_BUTTON = 'buttonPrevious'; + public readonly ALL_FIELDS = [this.PREVIOUS_BUTTON, this.NEXT_BUTTON]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public canGoPrevious(): promise.Promise { + const previousButton = element(by.id(this.PREVIOUS_BUTTON)); + if (previousButton) { + return previousButton.isEnabled(); + } + throw new Error('Couldn\'t get previous button'); + } + + public goNext(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public goPrevious(): void { + const nextButton = element(by.id(this.PREVIOUS_BUTTON)); + nextButton.click(); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/shared-app.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/shared-app.po.ts new file mode 100644 index 0000000..fea6c16 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/shared/shared-app.po.ts @@ -0,0 +1,91 @@ +import {browser, by, element, ElementFinder, protractor} from 'protractor'; +import { MaskingService } from 'src/app/shared/masking/masking.service'; + +export interface ElementIndexer { + [key: string]: ElementFinder; +} + +export class SharedAppPo { + public numbersList = '1234567890'; + public alphabetList = 'abcdfghijklmnopqrstuvwxyzABCDFGHIJKLMNOPQRSTUVWXYZ'; + public emptyPhone = MaskingService.emptyPhone; + + navigateTo() { + return browser.get('/'); + } + + navigateToSpecific(location: string) { + return browser.get(location); + } + + getHeaderBar() { + return element(by.css('lw-navigation')); + } + + getInputById(id: string): ElementFinder { + return element(by.id(id)); + } + + createRandomString(stringLength: number) { + const characters = this.alphabetList; + let result = ''; + for (let j = stringLength; j > 0; --j) { + result += characters[Math.floor(Math.random() * characters.length)]; + } + return result; + } + + createRandomNumber(numberLength: number) { + const numbers = this.numbersList; + let result = ''; + for (let j = numberLength; j > 0; --j) { + const nextNum = numbers[Math.floor(Math.random() * numbers.length)]; + if (nextNum !== '0') { + result += numbers[Math.floor(Math.random() * numbers.length)]; + } else { + j++; + } + } + return result; + } + + createRandomEmail(startLength = 10, endLength = 10): string { + const begin = this.createRandomString(startLength); + const end = this.createRandomString(endLength); + const email = [begin, '@', end, '.com']; + return email.join(''); + } + + getPageTitle() { + return browser.getTitle(); + } + + // used mainly in demos / development + sleep(time: number) { + browser.sleep(time); + } + + makeSSN(inputValue) { + return MaskingService.makeSSN(inputValue); + } + + makePhone(inputValue) { + return MaskingService.makePhone(inputValue); + } + + makeDOB(inputValue) { + return MaskingService.makeDOB(inputValue); + } + + makeNumber(inputValue) { + return MaskingService.makeNumber(inputValue); + } + + selectDropDownField(elementFinder: ElementFinder, optionNumber: number) { + const desiredOption = elementFinder.all(by.tagName('option')).get(optionNumber); + const EC = protractor.ExpectedConditions; + browser.wait(EC.visibilityOf(desiredOption), 5000).then(() => { + desiredOption.click(); + }); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/pages/page-disclosures.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/pages/page-disclosures.po.ts new file mode 100644 index 0000000..9df316b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/pages/page-disclosures.po.ts @@ -0,0 +1,34 @@ +import { by, element, promise } from 'protractor'; +import { PageObjectTools } from '../../shared/page-object-tools'; + +export class SignatureDisclosuresPagePo { + + public readonly PAGE_URL = '/loan-wizard/sl/pe/disclosures'; + + public readonly DISCLOSE_TRUTHFUL = 'truthful'; + public readonly DISCLOSE_CONTACT = 'contact'; + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly ALL_FIELDS = [ + this.DISCLOSE_TRUTHFUL, this.DISCLOSE_CONTACT, + this.NEXT_BUTTON]; + + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsMinimal(): void { + const truthful = element(by.id(this.DISCLOSE_TRUTHFUL)); + PageObjectTools.clickCheckBox(truthful); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/pages/page-one.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/pages/page-one.po.ts new file mode 100644 index 0000000..ff8097d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/pages/page-one.po.ts @@ -0,0 +1,92 @@ +import { by, element, promise } from 'protractor'; +import { PageObjectTools } from '../../shared/page-object-tools'; +import { TestInputSet, TestingInputType } from '../../shared/data-driven/test-input-command'; + +export class SignaturePageOnePo { + + public readonly PAGE_URL = '/loan-wizard/sl/pe/step-1'; + + public readonly LIMIT_REQUESTED = 'inputLimitRequested'; + public readonly PURPOSE_OF_LOAN_SIGNATURE = 'inputPurposeOfLoanSignature'; + public readonly ANOTHER_PERSON = 'inputApplicantType'; + public readonly LIVE_IN_WI = 'inputLivesInMaritalPropertyState'; + public readonly JOINT_CREDIT = 'inputConfirmJointCredit'; + public readonly SELECT_TERM = 'inputTerm'; + public readonly CO_APP_LIVE_IN_WI = 'inputCoAppLivesInMaritalPropertyState'; + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly MODAL_CLOSE_BUTTON = 'modalCloseButton'; + public readonly MODAL_OKAY_BUTTON = 'modalOkayButton'; + public readonly ALL_FIELDS = [this.SELECT_TERM, this.LIMIT_REQUESTED, this.ANOTHER_PERSON, this.LIVE_IN_WI, this.NEXT_BUTTON, this.PURPOSE_OF_LOAN_SIGNATURE]; + public readonly HIDDEN_FIELDS_1 = [this.JOINT_CREDIT, this.CO_APP_LIVE_IN_WI]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.LIMIT_REQUESTED, fieldValue: 1234, fieldType: TestingInputType.TEXT }, + { fieldId: this.PURPOSE_OF_LOAN_SIGNATURE, fieldValue: "Other", fieldType: TestingInputType.TEXT}, + { fieldId: this.ANOTHER_PERSON, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.LIVE_IN_WI, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.SELECT_TERM, fieldValue: 1, fieldType: TestingInputType.SELECT } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + { fieldId: this.LIMIT_REQUESTED, fieldValue: 1234, fieldType: TestingInputType.TEXT }, + { fieldId: this.PURPOSE_OF_LOAN_SIGNATURE, fieldValue: "Other", fieldType: TestingInputType.TEXT}, + { fieldId: this.ANOTHER_PERSON, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.LIVE_IN_WI, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.JOINT_CREDIT, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.CO_APP_LIVE_IN_WI, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.SELECT_TERM, fieldValue: 1, fieldType: TestingInputType.SELECT } + ]; + + public readonly JOINT_CREDIT_DATA: TestInputSet = [ + { fieldId: this.ANOTHER_PERSON, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.JOINT_CREDIT, fieldValue: 1, fieldType: TestingInputType.RADIO } + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public canClickDropdown(): promise.Promise { + const dropdown = element(by.id(this.SELECT_TERM)); + + if (dropdown) { + return dropdown.isEnabled(); + } + throw new Error('Couldn\'t click the dropbox'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsForFailureToConfirmJointCredit(): void { + PageObjectTools.fillFields(this.JOINT_CREDIT_DATA); + } + + public dismissModalByOkayButton(): void { + const modalOkayButton = element(by.id(this.MODAL_OKAY_BUTTON)); + modalOkayButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA); + } + + public getFocusedElement() { + return PageObjectTools.getFocusedElement(); + } + + public getFirstElement() { + return PageObjectTools.getElementsIdField(this.LIMIT_REQUESTED); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-full-spouses.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-full-spouses.e2e-spec.ts new file mode 100644 index 0000000..e8073c1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-full-spouses.e2e-spec.ts @@ -0,0 +1,156 @@ +import { browser } from 'protractor'; +import { SignaturePageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { SignatureDisclosuresPagePo } from './pages/page-disclosures.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { SpouseTypePagePo } from '../shared/pages/spouse-type-page.po'; + +describe('It should create a signature loan application for 4 full info individuals', () => { + + it('should accept maximum inputs for page 1 (Member/Loan Type)', () => { + const page = new SignaturePageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canClickDropdown()).toEqual(false); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.ALL_FIELDS, ...page.HIDDEN_FIELDS_1]); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length + page.HIDDEN_FIELDS_1.length); + expect(page.canClickDropdown()).toEqual(true); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 2.A (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsPrimaryMarriedButNotMarriedToCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.A (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalLivingInWI(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.A (Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept FULL INFO SPOUSE input for Applicant spouse type page', () => { + const page = new SpouseTypePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsFullInfoSpouse(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 2.B (Applicant\'s Spouse Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalNoMarital(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.B (Applicant\'s Spouse Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressSame(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.B (Applicant\'s Spouse Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2.C (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicantNonPrimary(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.C (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalLivingInWI(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.C (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept FULL INFO SPOUSE input for CoApplicant spouse type page', () => { + const page = new SpouseTypePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsFullInfoSpouse(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 2.D (CoApplicant\'s Spouse Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalNoMarital(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.D (CoApplicant\'s Spouse Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressSame(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.D (CoApplicant\'s Spouse Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should maximum inputs for page 5 (Disclosures)', () => { + const page = new SignatureDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-go-back.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-go-back.e2e-spec.ts new file mode 100644 index 0000000..eb43978 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-go-back.e2e-spec.ts @@ -0,0 +1,127 @@ +import { browser } from 'protractor'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { WizardPagePo } from '../shared/pages/wizard-page.po'; +import { SignaturePageOnePo } from './pages/page-one.po'; +import { SignatureDisclosuresPagePo } from './pages/page-disclosures.po'; + +describe('It should allow free navigation of a completed signature loan application', () => { + + it('should accept maximum inputs for page 1.', () => { + const page = new SignaturePageOnePo(); + browser.get(page.PAGE_URL); + expect(page.canClickDropdown()).toEqual(false); + page.fillFieldsMaximum(); + expect(page.canClickDropdown()).toEqual(true); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + page.fillFieldsMaximumCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + page.showEmploymentFields(); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2.A (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3.A (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressNotSame(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4.A (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + // Will no longer happen. + // expect(page.canGoNext()).toEqual(false); + // page.fillSecondIncome(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should maximum inputs for page 5 (Disclosures)', () => { + const page = new SignatureDisclosuresPagePo(); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + + it('should jump back to the beginning.', () => { + const urlList = [ + 'http://localhost:4200/loan-wizard/sl/pe/co-applicant-financial', + 'http://localhost:4200/loan-wizard/sl/pe/co-applicant-residential', + 'http://localhost:4200/loan-wizard/sl/pe/co-applicant-personal', + 'http://localhost:4200/loan-wizard/sl/pe/step-4', + 'http://localhost:4200/loan-wizard/sl/pe/step-3', + 'http://localhost:4200/loan-wizard/sl/pe/step-2', + 'http://localhost:4200/loan-wizard/sl/pe/step-1' + ]; + const page = new WizardPagePo(); + urlList.forEach((eachUrl) => { + expect(page.canGoPrevious()).toEqual(true); + page.goPrevious(); + browser.getCurrentUrl().then((newUrl) => { + expect(newUrl).toEqual(eachUrl); + }); + expect(page.canGoNext()).toEqual(true); + + }, this); + + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-maximum.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-maximum.e2e-spec.ts new file mode 100644 index 0000000..aac3cd9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-maximum.e2e-spec.ts @@ -0,0 +1,141 @@ +import { browser } from 'protractor'; +import { SignaturePageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { SignatureDisclosuresPagePo } from './pages/page-disclosures.po'; +import { protractor } from 'protractor/built/ptor'; + +describe('It should create a maximum signature loan application ', () => { + + it('should accept maximum inputs for page 1 (Member/Loan Type)', () => { + const page = new SignaturePageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canClickDropdown()).toEqual(false); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.ALL_FIELDS, ...page.HIDDEN_FIELDS_1]); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length + page.HIDDEN_FIELDS_1.length); + expect(page.canClickDropdown()).toEqual(true); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2.A (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + const EC = protractor.ExpectedConditions; + browser.wait(EC.visibilityOf(page.getFirstElement()), 5000).then(() => {}); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.COAPP_FIELDS); + expect(inputArray.length).toEqual(page.COAPP_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicantNonPrimary(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3.A (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressNotSame(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4.A (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should maximum inputs for page 5 (Disclosures)', () => { + const page = new SignatureDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-minimal.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-minimal.e2e-spec.ts new file mode 100644 index 0000000..2ebb1f1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-minimal.e2e-spec.ts @@ -0,0 +1,78 @@ +import { browser } from 'protractor'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { SignaturePageOnePo } from './pages/page-one.po'; +import { SignatureDisclosuresPagePo } from './pages/page-disclosures.po'; + +describe('It should create a minimal signature loan application ', () => { + + it('should accept basic inputs for page 1 (Member/Loan Type)', () => { + const page = new SignaturePageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + expect(page.canClickDropdown()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canClickDropdown()).toEqual(true); + expect(page.canGoNext()).toEqual(true); + page.fillFieldsForFailureToConfirmJointCredit(); + expect(page.canGoNext()).toEqual(false); + page.dismissModalByOkayButton(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 5 (Disclosures)', () => { + const page = new SignatureDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-tattle-spouses.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-tattle-spouses.e2e-spec.ts new file mode 100644 index 0000000..e4de95e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/signature/signature-tattle-spouses.e2e-spec.ts @@ -0,0 +1,132 @@ +import { browser } from 'protractor'; +import { SignaturePageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { SignatureDisclosuresPagePo } from './pages/page-disclosures.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { SpouseTypePagePo } from '../shared/pages/spouse-type-page.po'; +import { TattleSpousePagePo } from '../shared/pages/tattle-spouse-page.po'; + +describe('It should create a signature loan application for 2 full info individuals + 2 tattle spouses', () => { + + it('should accept maximum inputs for page 1 (Member/Loan Type)', () => { + const page = new SignaturePageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canClickDropdown()).toEqual(false); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.ALL_FIELDS, ...page.HIDDEN_FIELDS_1]); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length + page.HIDDEN_FIELDS_1.length); + expect(page.canClickDropdown()).toEqual(true); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 2.A (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsPrimaryMarriedButNotMarriedToCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.A (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalLivingInWI(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.A (Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept TATTLE SPOUSE input for Applicant spouse type page', () => { + const page = new SpouseTypePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsTattleSpouse(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for tattle spouse page (Applicant\'s Spouse Tattle Info)', () => { + const page = new TattleSpousePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + + it('should accept maximum inputs for page 2.C (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicantNonPrimary(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 3.C (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimalLivingInWI(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for page 4.C (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept TATTLE SPOUSE input for CoApplicant spouse type page', () => { + const page = new SpouseTypePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsTattleSpouse(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept minimum inputs for tattle spouse page (CoApplicant\'s Spouse Tattle Info)', () => { + const page = new TattleSpousePagePo(); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should maximum inputs for page 5 (Disclosures)', () => { + const page = new SignatureDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/pages/page-one.po.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/pages/page-one.po.ts new file mode 100644 index 0000000..b335c89 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/pages/page-one.po.ts @@ -0,0 +1,77 @@ +import { browser, by, element, ElementFinder, protractor, promise } from 'protractor'; +import { PageObjectTools } from '../../shared/page-object-tools'; +import { TestInputSet, TestInputCommand, TestingInputType } from '../../shared/data-driven/test-input-command'; + +export class VehiclePageOnePo { + + public readonly PAGE_URL = '/loan-wizard/ve/auto/step-1'; + + public readonly LIMIT_REQUESTED = 'inputLimitRequested'; + public readonly ANOTHER_PERSON = 'inputApplicantType'; + public readonly LIVE_IN_WI = 'inputLivesInMaritalPropertyState'; + public readonly JOINT_CREDIT = 'inputConfirmJointCredit'; + public readonly CO_APP_LIVE_IN_WI = 'inputCoAppLivesInMaritalPropertyState'; + public readonly NEXT_BUTTON = 'buttonNext'; + public readonly MODAL_CLOSE_BUTTON = 'modalCloseButton'; + public readonly MODAL_OKAY_BUTTON = 'modalOkayButton'; + public readonly ALL_FIELDS = [this.LIMIT_REQUESTED, this.ANOTHER_PERSON, this.LIVE_IN_WI, this.NEXT_BUTTON]; + public readonly HIDDEN_FIELDS_1 = [this.JOINT_CREDIT, this.CO_APP_LIVE_IN_WI]; + + public readonly MINIMAL_DATA: TestInputSet = [ + { fieldId: this.LIMIT_REQUESTED, fieldValue: 54321, fieldType: TestingInputType.TEXT }, + { fieldId: this.ANOTHER_PERSON, fieldValue: 1, fieldType: TestingInputType.RADIO }, + { fieldId: this.LIVE_IN_WI, fieldValue: 1, fieldType: TestingInputType.RADIO } + ]; + + public readonly MAXIMUM_DATA: TestInputSet = [ + { fieldId: this.LIMIT_REQUESTED, fieldValue: 54321, fieldType: TestingInputType.TEXT }, + { fieldId: this.ANOTHER_PERSON, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.LIVE_IN_WI, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.JOINT_CREDIT, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.CO_APP_LIVE_IN_WI, fieldValue: 0, fieldType: TestingInputType.RADIO } + ]; + + public readonly JOINT_CREDIT_DATA: TestInputSet = [ + { fieldId: this.ANOTHER_PERSON, fieldValue: 0, fieldType: TestingInputType.RADIO }, + { fieldId: this.JOINT_CREDIT, fieldValue: 1, fieldType: TestingInputType.RADIO } + ]; + + public canGoNext(): promise.Promise { + const nextButton = element(by.id(this.NEXT_BUTTON)); + if (nextButton) { + return nextButton.isEnabled(); + } + throw new Error('Couldn\'t get next button'); + } + + public submit(): void { + const nextButton = element(by.id(this.NEXT_BUTTON)); + nextButton.click(); + } + + public fillFieldsForFailureToConfirmJointCredit(): void { + PageObjectTools.fillFields(this.JOINT_CREDIT_DATA); + } + + public dismissModalByOkayButton(): void { + const modalOkayButton = element(by.id(this.MODAL_OKAY_BUTTON)); + modalOkayButton.click(); + } + + public fillFieldsMinimal(): void { + PageObjectTools.fillFields(this.MINIMAL_DATA); + } + + public fillFieldsMaximum(): void { + PageObjectTools.fillFields(this.MAXIMUM_DATA); + } + + public getFocusedElement() { + return PageObjectTools.getFocusedElement(); + } + + public getFirstElement() { + return PageObjectTools.getElementsIdField(this.LIMIT_REQUESTED); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/vehicle-maximum.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/vehicle-maximum.e2e-spec.ts new file mode 100644 index 0000000..2f9d6a5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/vehicle-maximum.e2e-spec.ts @@ -0,0 +1,152 @@ +import { browser, protractor } from 'protractor'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { VehiclePageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { CreditCardDisclosuresPagePo } from '../credit-card/pages/page-disclosures.po'; +import { VehicleCollateralPagePo } from '../shared/pages/vehicle-collateral-page.po'; + +describe('It should create a maximum vehicle application ', () => { + + it('should accept maximum inputs for page 1 (Member/Loan Type)', () => { + const page = new VehiclePageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.ALL_FIELDS, ...page.HIDDEN_FIELDS_1]); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length + page.HIDDEN_FIELDS_1.length); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicant(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS, page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.STARTING_FIELDS, ...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.STARTING_FIELDS, ...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 2.A (Co-Applicant Personal Info)', () => { + const page = new ContactPagePo(); + const EC = protractor.ExpectedConditions; + browser.wait(EC.visibilityOf(page.getFirstElement()), 5000).then(() => {}); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.COAPP_FIELDS); + expect(inputArray.length).toEqual(page.COAPP_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximumCoApplicantNonPrimary(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + + it('should accept maximum inputs for page 3.A (Co-applicant Residence)', () => { + const page = new ResidencePagePo(); + page.markAddressNotSame(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement(page.DEFAULT_PREFIX)); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + page.showMailingAddress(); + page.showSecondAddress(); + page.fillAdditionalFieldsMaximum(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4.A (Co-applicant Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.showOtherIncomeFields(); + page.showChildSupportFields(); + page.fillFieldsMaximum(); + expect(page.canGoNext()).toEqual(false); + page.fillChildSupportFields(); + page.fillOtherIncomeFields(); + page.markSingleIncomeNotEnough(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 4.B (Vehicle Collateral)', () => { + const page = new VehicleCollateralPagePo(); + const EC = protractor.ExpectedConditions; + browser.wait(EC.visibilityOf(page.getFirstElement()), 5000).then(() => {}); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canClickDropdown()).toEqual(false); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMaximum(); + expect(page.canClickDropdown()).toEqual(true); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept maximum inputs for page 5 (Disclosures)', () => { + const page = new CreditCardDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/vehicle-minimal.e2e-spec.ts b/LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/vehicle-minimal.e2e-spec.ts new file mode 100644 index 0000000..41a78b3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/src/vehicle/vehicle-minimal.e2e-spec.ts @@ -0,0 +1,96 @@ +import { browser } from 'protractor'; +import { PageObjectTools } from '../shared/page-object-tools'; +import { VehiclePageOnePo } from './pages/page-one.po'; +import { ContactPagePo } from '../shared/pages/contact-page.po'; +import { EmploymentPagePo } from '../shared/pages/employment-page.po'; +import { ResidencePagePo } from '../shared/pages/residence-page.po'; +import { SummaryPagePo } from '../shared/pages/summary-page.po'; +import { CreditCardDisclosuresPagePo } from '../credit-card/pages/page-disclosures.po'; +import { VehicleCollateralPagePo } from '../shared/pages/vehicle-collateral-page.po'; + +describe('It should create a minimal vehicle application ', () => { + + it('should accept basic inputs for page 1 (Member/Loan Type)', () => { + const page = new VehiclePageOnePo(); + browser.get(page.PAGE_URL); + browser.sleep(1000); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.fillFieldsForFailureToConfirmJointCredit(); + expect(page.canGoNext()).toEqual(false); + page.dismissModalByOkayButton(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 2 (Personal Info)', () => { + const page = new ContactPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 3 (Residence)', () => { + const page = new ResidencePagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for page 4 (Employment)', () => { + const page = new EmploymentPagePo(); + let inputArray = PageObjectTools.findFieldsInPageByIdArray(page.STARTING_FIELDS, page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual(page.STARTING_FIELDS.length); + expect(page.canGoNext()).toEqual(true); + page.showEmploymentFields(); + inputArray = PageObjectTools.findFieldsInPageByIdArray([...page.STARTING_FIELDS, ...page.EMPLOYMENT_FIELDS], page.DEFAULT_PREFIX); + expect(inputArray.length).toEqual([...page.STARTING_FIELDS, ...page.EMPLOYMENT_FIELDS].length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should accept basic inputs for collateral page 4.A (Vehicle Collateral)', () => { + const page = new VehicleCollateralPagePo(); + expect(page.getFocusedElement()).toEqual(page.getFirstElement()); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canClickDropdown()).toEqual(false); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canClickDropdown()).toEqual(true); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + + it('should accept basic inputs for page 5 (Disclosures)', () => { + const page = new CreditCardDisclosuresPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + expect(page.canGoNext()).toEqual(false); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + page.submit(); + }); + + it('should show application information on page 6 (Summary)', () => { + const page = new SummaryPagePo(); + const inputArray = PageObjectTools.findFieldsInPageByIdArray(page.ALL_FIELDS); + expect(inputArray.length).toEqual(page.ALL_FIELDS.length); + page.fillFieldsMinimal(); + expect(page.canGoNext()).toEqual(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/e2e/tsconfig.e2e.json b/LCU.WebApps.External.OnlineLoanApp/e2e/tsconfig.e2e.json new file mode 100644 index 0000000..a6dd622 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/e2e/tsconfig.e2e.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/base/Deployment.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/base/Deployment.yaml new file mode 100644 index 0000000..5058052 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/base/Deployment.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: online-loan-app +spec: + replicas: 1 + selector: + matchLabels: + app: online-loan-app + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: online-loan-app + spec: + containers: + - name: online-loan-app + env: [] + image: online-loan-app + resources: + limits: + memory: "1536Mi" + requests: + memory: "1536Mi" + cpu: "100m" + ports: + - containerPort: 80 + livenessProbe: + initialDelaySeconds: 5 + periodSeconds: 5 + httpGet: + path: / + port: 80 + readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 5 + httpGet: + path: / + port: 80 diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/base/Service.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/base/Service.yaml new file mode 100644 index 0000000..f3d6d80 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/base/Service.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: online-loan-app +spec: + selector: + app: online-loan-app + ports: + - port: 80 + targetPort: 80 \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/base/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/base/kustomization.yaml new file mode 100644 index 0000000..2a3c36c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/base/kustomization.yaml @@ -0,0 +1,8 @@ +resources: + - Deployment.yaml + - Service.yaml + - virtual-service.yaml + +images: +- name: online-loan-app + newName: landmarkcu/lcu.api.internal.onlineloanapp \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/base/virtual-service.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/base/virtual-service.yaml new file mode 100644 index 0000000..abaf011 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/base/virtual-service.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: online-loan-app +spec: + hosts: [] + gateways: + - istio-system/ingress-gateway + http: + - name: online-loan-app + match: + - uri: + prefix: / + route: + - destination: + host: online-loan-app + port: + number: 80 diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/common/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/common/kustomization.yaml new file mode 100644 index 0000000..ac472c6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/common/kustomization.yaml @@ -0,0 +1,7 @@ +resources: + - ../../../base +patchesStrategicMerge: + - set_appsettings_deployment.yaml +images: +- name: online-loan-app + newName: landmarkcu/lcu.api.internal.onlineloanapp-onlineloanapp diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/common/set_appsettings_deployment.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/common/set_appsettings_deployment.yaml new file mode 100644 index 0000000..f39b072 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/common/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: online-loan-app +spec: + template: + spec: + containers: + - name: online-loan-app + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Development" diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/kustomization.yaml new file mode 100644 index 0000000..25a65b6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - normal diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/configmap.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/configmap.yaml new file mode 100644 index 0000000..894aa6c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: online-loan-app-configmap +data: + primary.json: | + { + "Primary": { + "SeqUrl": "https://qa-seq.lcu-internal.com", + "CorsAllowedOrigins": [ + "http://localhost:3000", + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://qa-api.lcu-internal.com" + ] + } + } \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/kustomization.yaml new file mode 100644 index 0000000..6dd0c3b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/kustomization.yaml @@ -0,0 +1,5 @@ +resources: + - configmap.yaml + - ../common +patchesStrategicMerge: + - set_virtual_service.yaml diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/set_virtual_service.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/set_virtual_service.yaml new file mode 100644 index 0000000..1891c1f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/local/normal/set_virtual_service.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: online-loan-app +spec: + hosts: + - online-loan-app.apps.azure.lcu-internal.com diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/common/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/common/kustomization.yaml new file mode 100644 index 0000000..aa8191d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/common/kustomization.yaml @@ -0,0 +1,7 @@ +resources: +- ../../../base +patchesStrategicMerge: +- set_appsettings_deployment.yaml +images: +- name: online-loan-app + newName: landmarkcu/lcu.api.internal.onlineloanapp-onlineloanapp \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/common/set_appsettings_deployment.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/common/set_appsettings_deployment.yaml new file mode 100644 index 0000000..f8dfd0e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/common/set_appsettings_deployment.yaml @@ -0,0 +1,13 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: online-loan-app +spec: + template: + spec: + containers: + - name: online-loan-app + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/kustomization.yaml new file mode 100644 index 0000000..25a65b6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - normal diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/configmap.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/configmap.yaml new file mode 100644 index 0000000..d5e3794 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/configmap.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: online-loan-app-configmap +data: + primary.json: | + { + "Primary": { + "SeqUrl": "https://seq.lcu-internal.com", + "CorsAllowedOrigins": [ + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://api.lcu-internal.com" + ] + } + } diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/kustomization.yaml new file mode 100644 index 0000000..7fa3e2a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/kustomization.yaml @@ -0,0 +1,5 @@ +resources: +- configmap.yaml +- ../common +patchesStrategicMerge: +- set_virtual_service.yaml diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/set_virtual_service.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/set_virtual_service.yaml new file mode 100644 index 0000000..0290bd7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/prd/normal/set_virtual_service.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: online-loan-app +spec: + hosts: + - prd-online-loan-app.apps.azure.lcu-internal.com + http: + - name: online-loan-app + match: + - uri: + prefix: / + route: + - destination: + host: online-loan-app + port: + number: 80 diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/common/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/common/kustomization.yaml new file mode 100644 index 0000000..aa8191d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/common/kustomization.yaml @@ -0,0 +1,7 @@ +resources: +- ../../../base +patchesStrategicMerge: +- set_appsettings_deployment.yaml +images: +- name: online-loan-app + newName: landmarkcu/lcu.api.internal.onlineloanapp-onlineloanapp \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/common/set_appsettings_deployment.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/common/set_appsettings_deployment.yaml new file mode 100644 index 0000000..d66f027 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/common/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: online-loan-app +spec: + template: + spec: + containers: + - name: online-loan-app + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Development" \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/kustomization.yaml new file mode 100644 index 0000000..a738d0b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - normal \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/configmap.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/configmap.yaml new file mode 100644 index 0000000..1b8eedd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: online-loan-app-configmap +data: + primary.json: | + { + "Primary": { + "SeqUrl": "https://qa-seq.lcu-internal.com", + "CorsAllowedOrigins": [ + "http://localhost:3000", + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://qa-api.lcu-internal.com" + ] + } + } diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/kustomization.yaml new file mode 100644 index 0000000..7fa3e2a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/kustomization.yaml @@ -0,0 +1,5 @@ +resources: +- configmap.yaml +- ../common +patchesStrategicMerge: +- set_virtual_service.yaml diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/set_virtual_service.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/set_virtual_service.yaml new file mode 100644 index 0000000..e874e33 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/qa/normal/set_virtual_service.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: online-loan-app +spec: + hosts: + - qa-online-loan-app.apps.azure.lcu-internal.com + http: + - name: online-loan-app + match: + - uri: + prefix: / + route: + - destination: + host: online-loan-app + port: + number: 80 \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/common/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/common/kustomization.yaml new file mode 100644 index 0000000..d355113 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/common/kustomization.yaml @@ -0,0 +1,7 @@ +resources: +- ../../../base +patchesStrategicMerge: +- set_appsettings_deployment.yaml +images: +- name: online-loan-app + newName: landmarkcu/lcu.api.internal.onlineloanapp-onlineloanapp diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/common/set_appsettings_deployment.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/common/set_appsettings_deployment.yaml new file mode 100644 index 0000000..d0b21e5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/common/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: online-loan-app +spec: + template: + spec: + containers: + - name: online-loan-app + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Staging" diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/kustomization.yaml new file mode 100644 index 0000000..25a65b6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - normal diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/configmap.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/configmap.yaml new file mode 100644 index 0000000..1dcb1d9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/configmap.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: online-loan-app-configmap +data: + primary.json: | + { + "Primary": { + "SeqUrl": "https://uat-seq.lcu-internal.com", + "CorsAllowedOrigins": [ + "http://localhost:3000", + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://uat-api.lcu-internal.com" + ] + } + } diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/kustomization.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/kustomization.yaml new file mode 100644 index 0000000..7fa3e2a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/kustomization.yaml @@ -0,0 +1,5 @@ +resources: +- configmap.yaml +- ../common +patchesStrategicMerge: +- set_virtual_service.yaml diff --git a/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/set_virtual_service.yaml b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/set_virtual_service.yaml new file mode 100644 index 0000000..0064ab3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/kustomize/overlays/uat/normal/set_virtual_service.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: online-loan-app +spec: + hosts: + - uat-online-loan-app.apps.azure.lcu-internal.com + http: + - name: online-loan-app + match: + - uri: + prefix: / + route: + - destination: + host: online-loan-app + port: + number: 80 diff --git a/LCU.WebApps.External.OnlineLoanApp/package-lock.json b/LCU.WebApps.External.OnlineLoanApp/package-lock.json new file mode 100644 index 0000000..4e6098a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/package-lock.json @@ -0,0 +1,30466 @@ +{ + "name": "ui", + "version": "4.11.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "ui", + "version": "4.11.0", + "hasInstallScript": true, + "dependencies": { + "@angular/animations": "^6.0.3", + "@angular/cdk": "6.4.7", + "@angular/common": "^6.0.3", + "@angular/compiler": "^6.0.3", + "@angular/core": "^6.0.3", + "@angular/forms": "^6.0.3", + "@angular/http": "^6.0.3", + "@angular/material": "6.4.7", + "@angular/platform-browser": "^6.0.3", + "@angular/platform-browser-dynamic": "^6.0.3", + "@angular/router": "^6.0.3", + "@fortawesome/fontawesome": "^1.1.8", + "@fortawesome/fontawesome-free-regular": "^5.0.13", + "@fortawesome/fontawesome-free-solid": "^5.0.13", + "@ng-bootstrap/ng-bootstrap": "^2.1.0", + "@ngx-translate/core": "^10.0.2", + "@ngx-translate/http-loader": "^3.0.1", + "angular2-text-mask": "^9.0.0", + "bootstrap": "^4.1.1", + "core-js": "^2.5.4", + "file-saver": "^2.0.0", + "fs-extra": "^7.0.0", + "git-describe": "^4.0.3", + "microsoft-adal-angular6": "^1.0.0", + "rxjs": "^6.0.0", + "text-mask-addons": "^3.7.2", + "zone.js": "^0.8.26" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~0.8.9", + "@angular/cli": "~6.0.8", + "@angular/compiler-cli": "^6.0.3", + "@angular/language-service": "^6.0.3", + "@types/jasmine": "~2.8.6", + "@types/jasminewd2": "~2.0.3", + "@types/node": "~8.9.4", + "codelyzer": "~4.2.1", + "commitizen": "^3.0.4", + "cz-conventional-changelog": "^2.1.0", + "jasmine-core": "~2.99.1", + "jasmine-spec-reporter": "~4.2.1", + "karma": "~1.7.1", + "karma-chrome-launcher": "^2.2.0", + "karma-coverage-istanbul-reporter": "~2.0.0", + "karma-edge-launcher": "^0.4.2", + "karma-firefox-launcher": "^1.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-jasmine": "~1.1.1", + "karma-jasmine-html-reporter": "^0.2.2", + "karma-junit-reporter": "^1.2.0", + "karma-spec-reporter": "^0.0.36", + "ng-bullet": "^1.0.3", + "protractor": "~5.3.0", + "puppeteer": "^1.5.0", + "sass": "^1.49.9", + "standard-version": "^4.4.0", + "ts-node": "~5.0.1", + "tslint": "~5.9.1", + "typescript": "~2.7.2", + "validate-commit-msg": "^2.14.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.6.8.tgz", + "integrity": "sha512-ZKTm/zC61iY9IBHOEAKoMSzZpvhkmv+1O/HHzpHEuR551jCzu6vSyCmMY9Z7GBcccscCV+hjeSMwgFrFRcqlkw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "0.6.8", + "rxjs": "^6.0.0" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.8.9.tgz", + "integrity": "sha512-J6o0MwIG1cJT29p87c7uUn7NY3QLEoQOVw4VXWM9cqG9bv99VK7f7eOSDhHJbXn7Snm4XYrye0zRa3RFXhMG+A==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.8.9", + "@angular-devkit/build-optimizer": "0.8.9", + "@angular-devkit/build-webpack": "0.8.9", + "@angular-devkit/core": "0.8.9", + "@ngtools/webpack": "6.2.9", + "ajv": "6.4.0", + "autoprefixer": "8.6.5", + "circular-dependency-plugin": "5.0.2", + "clean-css": "4.2.1", + "copy-webpack-plugin": "4.5.2", + "file-loader": "1.1.11", + "glob": "7.1.3", + "html-webpack-plugin": "3.2.0", + "istanbul": "0.4.5", + "istanbul-instrumenter-loader": "3.0.1", + "karma-source-map-support": "1.3.0", + "less": "3.8.1", + "less-loader": "4.1.0", + "license-webpack-plugin": "1.5.0", + "loader-utils": "1.1.0", + "mini-css-extract-plugin": "0.4.4", + "minimatch": "3.0.4", + "opn": "5.4.0", + "parse5": "4.0.0", + "portfinder": "1.0.19", + "postcss": "6.0.23", + "postcss-import": "11.1.0", + "postcss-loader": "2.1.6", + "postcss-url": "7.3.2", + "raw-loader": "0.5.1", + "rxjs": "6.2.2", + "sass-loader": "7.1.0", + "semver": "5.6.0", + "source-map-loader": "0.2.4", + "source-map-support": "0.5.9", + "stats-webpack-plugin": "0.6.2", + "style-loader": "0.21.0", + "stylus": "0.54.5", + "stylus-loader": "3.0.2", + "tree-kill": "1.2.1", + "uglifyjs-webpack-plugin": "1.3.0", + "url-loader": "1.1.2", + "webpack": "4.16.4", + "webpack-dev-middleware": "3.4.0", + "webpack-dev-server": "3.1.14", + "webpack-merge": "4.1.4", + "webpack-sources": "1.3.0", + "webpack-subresource-integrity": "1.1.0-rc.4" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + }, + "optionalDependencies": { + "node-sass": "^4.9.3" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.8.9.tgz", + "integrity": "sha512-2tiGPkvJyFY/G3a27uC8r6Jj3H5m8SxjMqhjNUQ5AtNumweTBPt3YIYMNAvHUmxG0nA9upDolVXFmoQGK9AhKQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "0.8.9", + "rxjs": "6.2.2" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.9.tgz", + "integrity": "sha512-Umax3YKBPTQy360TeoSNaIIOJOKoXvN/S2WNTV8wDjSWWNiWLTIlckWMb9DVsafAifjUi0mtOLRFuM4YatKgTw==", + "dev": true, + "dependencies": { + "ajv": "6.4.0", + "chokidar": "2.0.4", + "rxjs": "6.2.2", + "source-map": "0.5.7" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha512-uWK8ISUH3jdo9gTIgylnj4QQDKFL6SbQcx9LtwXJ+2biBCSNIhc41aYJO0W1/w+6tEMPsopQ0cOCnWX39u5o0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "optional": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + }, + "optionalDependencies": { + "fsevents": "^1.2.2" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "optional": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "optional": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "optional": true, + "dependencies": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "optional": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "optional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "optional": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "optional": true, + "dependencies": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/node-gyp/node_modules/semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/node-sass": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", + "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash": "^4.17.15", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.13.2", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "2.2.5", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "bin": { + "node-sass": "bin/node-sass" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/opn": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", + "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "optional": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "optional": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "optional": true + }, + "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", + "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/sass-graph": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", + "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", + "dev": true, + "optional": true, + "dependencies": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^13.3.2" + }, + "bin": { + "sassgraph": "bin/sassgraph" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "optional": true, + "dependencies": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/scss-tokenizer/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "optional": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/string-width/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "optional": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "deprecated": "This version of tar is no longer supported, and will not receive security updates. Please upgrade asap.", + "dev": true, + "optional": true, + "dependencies": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha512-SoboS4c924cg+wR2vxl8fospPPli3ZmVPIkRpJEWcrGIPeE8Tr3m9zNIyjYKn9YlF8EgiXQDCy3XVZxSFNjh8A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "optional": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "optional": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "optional": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "optional": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/@angular-devkit/build-optimizer": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.8.9.tgz", + "integrity": "sha512-h8u5iAhSmt0TsLDZXZCmOkXZDMgP2itLkgZvOIsGInyMAESJuWK4P1qegMSv2R5ELOsinJiuhe218M4K2enEdA==", + "dev": true, + "dependencies": { + "loader-utils": "1.1.0", + "source-map": "0.5.7", + "typescript": "2.9.2", + "webpack-sources": "1.3.0" + }, + "bin": { + "build-optimizer": "src/build-optimizer/cli.js", + "purify": "src/purify/cli.js" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular-devkit/build-optimizer/node_modules/typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.8.9.tgz", + "integrity": "sha512-2csJ6utodPSLABTXfBLymYLrndJURF3xVqVjEDzUFl9zLqK1YOkKH4XPr12vfH8SfAtvzIutNLRxBtAuWJmDlw==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.8.9", + "@angular-devkit/core": "0.8.9", + "rxjs": "6.2.2" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + }, + "peerDependencies": { + "webpack": "^4.6.0", + "webpack-dev-server": "^3.1.4" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.8.9.tgz", + "integrity": "sha512-2tiGPkvJyFY/G3a27uC8r6Jj3H5m8SxjMqhjNUQ5AtNumweTBPt3YIYMNAvHUmxG0nA9upDolVXFmoQGK9AhKQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "0.8.9", + "rxjs": "6.2.2" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.9.tgz", + "integrity": "sha512-Umax3YKBPTQy360TeoSNaIIOJOKoXvN/S2WNTV8wDjSWWNiWLTIlckWMb9DVsafAifjUi0mtOLRFuM4YatKgTw==", + "dev": true, + "dependencies": { + "ajv": "6.4.0", + "chokidar": "2.0.4", + "rxjs": "6.2.2", + "source-map": "0.5.7" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha512-uWK8ISUH3jdo9gTIgylnj4QQDKFL6SbQcx9LtwXJ+2biBCSNIhc41aYJO0W1/w+6tEMPsopQ0cOCnWX39u5o0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + }, + "optionalDependencies": { + "fsevents": "^1.2.2" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", + "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha512-SoboS4c924cg+wR2vxl8fospPPli3ZmVPIkRpJEWcrGIPeE8Tr3m9zNIyjYKn9YlF8EgiXQDCy3XVZxSFNjh8A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.6.8.tgz", + "integrity": "sha512-rkIa1OSVWTt4g9leLSK/PsqOj3HZbDKHbZjqlslyfVa3AyCeiumFoOgViOVXlYgPX3HHDbE5uH24nyUWSD8uww==", + "dev": true, + "dependencies": { + "ajv": "~6.4.0", + "chokidar": "^2.0.3", + "rxjs": "^6.0.0", + "source-map": "^0.5.6" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular-devkit/core/node_modules/ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha512-uWK8ISUH3jdo9gTIgylnj4QQDKFL6SbQcx9LtwXJ+2biBCSNIhc41aYJO0W1/w+6tEMPsopQ0cOCnWX39u5o0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" + } + }, + "node_modules/@angular-devkit/core/node_modules/uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha512-SoboS4c924cg+wR2vxl8fospPPli3ZmVPIkRpJEWcrGIPeE8Tr3m9zNIyjYKn9YlF8EgiXQDCy3XVZxSFNjh8A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.6.8.tgz", + "integrity": "sha512-R4YqAUdo62wtrhX/5HSRGSKXNTWqfQb66ZE6m8jj6GEJNFKdNXMdxOchxr07LCiKTxfh1w6G3nGzxIsu/+D4KA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "0.6.8", + "rxjs": "^6.0.0" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular/animations": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-6.0.4.tgz", + "integrity": "sha512-Ro1XEwShk8XRAogfu73fKLTBLnND1s+MGhN+ymwr7ib9hqxVr7jMNE+MXPqG2/1BX9c+7NHb30B8G4woQn62vw==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/core": "6.0.4" + } + }, + "node_modules/@angular/cdk": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.4.7.tgz", + "integrity": "sha512-18x0U66fLD5kGQWZ9n3nb75xQouXlWs7kUDaTd8HTrHpT1s2QIAqlLd1KxfrYiVhsEC2jPQaoiae7VnBlcvkBg==", + "dependencies": { + "tslib": "^1.7.1" + }, + "peerDependencies": { + "@angular/common": ">=6.0.0-beta.0 <7.0.0", + "@angular/core": ">=6.0.0-beta.0 <7.0.0" + } + }, + "node_modules/@angular/cli": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-6.0.8.tgz", + "integrity": "sha512-DhH1Zq5Yonthw6zh6W07fhf+9XrAZbD1fcQ0MrmbxlieCfLlTAdBqyK2LavFCKwSZkUMLF6UHM3+jiNRVZSSIg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@angular-devkit/architect": "0.6.8", + "@angular-devkit/core": "0.6.8", + "@angular-devkit/schematics": "0.6.8", + "@schematics/angular": "0.6.8", + "@schematics/update": "0.6.8", + "opn": "~5.3.0", + "resolve": "^1.1.7", + "rxjs": "^6.0.0", + "semver": "^5.1.0", + "silent-error": "^1.0.0", + "symbol-observable": "^1.2.0", + "yargs-parser": "^10.0.0" + }, + "bin": { + "ng": "bin/ng" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@angular/cli/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@angular/cli/node_modules/yargs-parser": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.0.0.tgz", + "integrity": "sha512-+DHejWujTVYeMHLff8U96rLc4uE4Emncoftvn5AjhB1Jw1pWxLzgBUT/WYbPrHmy6YPEBTZQx5myHhVcuuu64g==", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/@angular/common": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-6.0.4.tgz", + "integrity": "sha512-z2UE6a43u49omsqjzwdHhwD+wpCPauuD13FMkVQMLwzugKhNri0LjMk5I0I9xLz9OyXICm2B+wPAQN4d07savg==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/core": "6.0.4", + "rxjs": "^6.0.0" + } + }, + "node_modules/@angular/compiler": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-6.0.4.tgz", + "integrity": "sha512-qwjuPacuRKUroD+xev653mljV9Jwhi2tvwGo/cVadq0isJ2rVH1hO6sa1FWFnNf1KPnyolMobZlMgg8DFW/yRw==", + "dependencies": { + "tslib": "^1.9.0" + } + }, + "node_modules/@angular/compiler-cli": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-6.0.4.tgz", + "integrity": "sha512-bkN21JJrsbIdXNfOc9I9iB7AETUe4/QvApC25R3/pERSyhkK7UNNHTqg4FY5xMdSxlZd1ccWj8rvSbS+hFbTBw==", + "dev": true, + "dependencies": { + "chokidar": "^1.4.2", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "tsickle": "^0.29.0" + }, + "bin": { + "ng-xi18n": "src/extract_i18n.js", + "ngc": "src/main.js" + }, + "engines": { + "node": ">=8.0" + }, + "peerDependencies": { + "@angular/compiler": "6.0.4", + "typescript": ">=2.7.2 <2.8" + } + }, + "node_modules/@angular/compiler-cli/node_modules/anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "dependencies": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "dependencies": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + }, + "optionalDependencies": { + "fsevents": "^1.0.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/@angular/core": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-6.0.4.tgz", + "integrity": "sha512-WCW8wyRoIKkjGjOjVLFpUyhQw/K0//s3W0qo1/vH6m9njC63jP/VP+2Xnt8bdtgeET2NYUeyl+w/0vUXqYlnEA==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "rxjs": "^6.0.0", + "zone.js": "~0.8.26" + } + }, + "node_modules/@angular/forms": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-6.0.4.tgz", + "integrity": "sha512-JcKyCCkAwF3NlWuwPxtpRhFX/8QbFUkA4W0N62L+Odm8G0/P4QhMw/99o5kRs5xrsQRVyFlGvdYOrkgDyyZkGg==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/common": "6.0.4", + "@angular/core": "6.0.4", + "@angular/platform-browser": "6.0.4", + "rxjs": "^6.0.0" + } + }, + "node_modules/@angular/http": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-6.0.4.tgz", + "integrity": "sha512-bq7cS0/5hM3Af3FolEe7HpKP19/7MJWIPGi5k9rYCGM4dXKFiKa+LA/SbXE0DMiKxhAQbZDwQ9HN04r70DRyBQ==", + "deprecated": "Package no longer supported. Use @angular/common instead, see https://angular.io/guide/deprecations#angularhttp", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/core": "6.0.4", + "@angular/platform-browser": "6.0.4", + "rxjs": "^6.0.0" + } + }, + "node_modules/@angular/language-service": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-6.0.4.tgz", + "integrity": "sha512-LyFh/sFtnyjJJYO5UKM31nGSSkmGYFMwqzwbG+l0QNQXNoa9jj+OApvu0oOY5F0WP9DBrOAzj74j2M5olprtMQ==", + "dev": true + }, + "node_modules/@angular/material": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-6.4.7.tgz", + "integrity": "sha512-SdNx7Xovi24Kw9eU6lkLhY/7f2M7L9F+/uh6XuPr4jbGgCUVVpeeVI5ztZhsZRbj1sN+/r1p5w8u62apWWl5Ww==", + "dependencies": { + "tslib": "^1.7.1" + }, + "optionalDependencies": { + "parse5": "^5.0.0" + }, + "peerDependencies": { + "@angular/cdk": "6.4.7", + "@angular/common": ">=6.0.0-beta.0 <7.0.0", + "@angular/core": ">=6.0.0-beta.0 <7.0.0" + } + }, + "node_modules/@angular/material/node_modules/parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "optional": true + }, + "node_modules/@angular/platform-browser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-6.0.4.tgz", + "integrity": "sha512-4y6rSqOLuBs+RL0CIM+RZ5T8a5Tm1NisZcTUGvM8lVYPNkqTzNfHQXhBq8mTgRw9JopEwOj+UEqBbgrNX715yQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/common": "6.0.4", + "@angular/core": "6.0.4" + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.0.4.tgz", + "integrity": "sha512-uh7MoQi9kyKPFHrD7HPOU5gWyhF4pPJuGltyMTOtM/f5TNL9QQI2EugijKnnSFLQ8+7UQjmFAVeRqH9jylhIyQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/common": "6.0.4", + "@angular/compiler": "6.0.4", + "@angular/core": "6.0.4", + "@angular/platform-browser": "6.0.4" + } + }, + "node_modules/@angular/router": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-6.0.4.tgz", + "integrity": "sha512-IKUtbbRtHN8I+Hp8/pLqBPyXJmQHrPFGzKU3H43TovD++mmT5AaYVCeJgkGUB//wjtLN3y0X/yrmyWFWcN35Gw==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/common": "6.0.4", + "@angular/core": "6.0.4", + "@angular/platform-browser": "6.0.4", + "rxjs": "^6.0.0" + } + }, + "node_modules/@fortawesome/fontawesome": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome/-/fontawesome-1.1.8.tgz", + "integrity": "sha512-c0/MtkPVT0fmiFcCyYoPjkG9PkMOvfrZw2+0BaJ+Rh6UEcK1AR/LaRgrHHjUkbAbs9LXxQJhFS8CJ4uSnK2+JA==", + "deprecated": "This package is deprecated. See https://git.io/fNCzJ for information about upgrading.", + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.1.7" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.1.7.tgz", + "integrity": "sha512-ego8jRVSHfq/iq4KRZJKQeUAdi3ZjGNrqw4oPN3fNdvTBnLCSntwVCnc37bsAJP9UB8MhrTfPnZYxkv2vpS4pg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-free-regular": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-regular/-/fontawesome-free-regular-5.0.13.tgz", + "integrity": "sha512-36lz9Idww1L4QaaTcv7GZiOeIP9emJFDUsedvRovI10kmwyd6rN0PKkIjnq0FB4foLhX4Rou8vnbCCmjtqiLug==", + "deprecated": "This package is deprecated. See https://git.io/fNCzJ for information about upgrading.", + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.1.7" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-free-solid": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-solid/-/fontawesome-free-solid-5.0.13.tgz", + "integrity": "sha512-b+krVnqkdDt52Yfev0x0ZZgtxBQsLw00Zfa3uaVWIDzpNZVtrEXuxldUSUaN/ihgGhSNi8VpvDAdNPVgCKOSxw==", + "deprecated": "This package is deprecated. See https://git.io/fNCzJ for information about upgrading.", + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.1.7" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@ng-bootstrap/ng-bootstrap": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-2.1.0.tgz", + "integrity": "sha512-ElqT/uayvnAbA9Klt0IZSbQ+BZtyEPdGDGojmYWQEFDNmTmismkM9Cohx4Hy6PakGAItCy7Ghu1isR8ZKkdtnw==", + "peerDependencies": { + "@angular/common": "^6.0.0", + "@angular/core": "^6.0.0", + "@angular/forms": "^6.0.0", + "rxjs": "^6.0.0" + } + }, + "node_modules/@ngtools/webpack": { + "version": "6.2.9", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-6.2.9.tgz", + "integrity": "sha512-wZ9ROI4FdA9gnx21ULtA8u6Gmtig3BbT6EfUh2uYisjyL4P9k2p22sfY/Txwu/InnF4LDHOs5xZutOnGvC87vw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "0.8.9", + "rxjs": "6.2.2", + "tree-kill": "1.2.1", + "webpack-sources": "1.3.0" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + }, + "peerDependencies": { + "typescript": "~2.4.0 || ~2.5.0 || ~2.6.0 || ~2.7.0 || ~2.8.0 || ~2.9.0", + "webpack": "^4.0.0" + } + }, + "node_modules/@ngtools/webpack/node_modules/@angular-devkit/core": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.9.tgz", + "integrity": "sha512-Umax3YKBPTQy360TeoSNaIIOJOKoXvN/S2WNTV8wDjSWWNiWLTIlckWMb9DVsafAifjUi0mtOLRFuM4YatKgTw==", + "dev": true, + "dependencies": { + "ajv": "6.4.0", + "chokidar": "2.0.4", + "rxjs": "6.2.2", + "source-map": "0.5.7" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@ngtools/webpack/node_modules/ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha512-uWK8ISUH3jdo9gTIgylnj4QQDKFL6SbQcx9LtwXJ+2biBCSNIhc41aYJO0W1/w+6tEMPsopQ0cOCnWX39u5o0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" + } + }, + "node_modules/@ngtools/webpack/node_modules/chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + }, + "optionalDependencies": { + "fsevents": "^1.2.2" + } + }, + "node_modules/@ngtools/webpack/node_modules/rxjs": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", + "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@ngtools/webpack/node_modules/uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha512-SoboS4c924cg+wR2vxl8fospPPli3ZmVPIkRpJEWcrGIPeE8Tr3m9zNIyjYKn9YlF8EgiXQDCy3XVZxSFNjh8A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/@ngx-translate/core": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-10.0.2.tgz", + "integrity": "sha512-7nM3DrJaqKswwtJlbu2kuKNl+hE8Isr18sKsKvGGpSxQk+G0gO0reDlx2PhUNus7TJTkA1C59vU/JoN8hIvZ4g==", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/core": ">=6.0.0", + "rxjs": ">=6.0.0" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-3.0.1.tgz", + "integrity": "sha1-ILD5i8bCUyESnT4zAqs8xInApCo=", + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/common": ">=6.0.0-rc.0", + "@angular/core": ">=6.0.0-rc.0", + "@ngx-translate/core": ">=10.0.0" + } + }, + "node_modules/@schematics/angular": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.6.8.tgz", + "integrity": "sha512-9kRphqTYG5Df/I8fvnT1zMsw0YNDPO9tl18tQZXj4am4raT7l9UCr+WkwJdlBoA5pwG6baWE9sL0iGWV/bzF/g==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "0.6.8", + "@angular-devkit/schematics": "0.6.8", + "typescript": ">=2.6.2 <2.8" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@schematics/update": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.6.8.tgz", + "integrity": "sha512-1Uq7LYnwL2wBwGVCgNz76QAR13ghAk+2vDDHOi+VX5+usHManxydrpoMGeX66OBPd+y5D3D2MFb+8mYHE7mygg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "0.6.8", + "@angular-devkit/schematics": "0.6.8", + "npm-registry-client": "^8.5.1", + "rxjs": "^6.0.0", + "semver": "^5.3.0", + "semver-intersect": "^1.1.2" + }, + "engines": { + "node": ">= 8.9.0", + "npm": ">= 5.5.1" + } + }, + "node_modules/@types/adal": { + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@types/adal/-/adal-1.0.29.tgz", + "integrity": "sha512-6kvyTWzue5lJ8xECAgVdMrs5+UEyPeflzU/L83QdufGnvjdyCiCWVgkfinMB7133ik/x+02T0q1070Z6/ZK6eQ==", + "deprecated": "'@types/adal' is now '@types/adal-angular'", + "dependencies": { + "@types/angular": "*" + } + }, + "node_modules/@types/angular": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.51.tgz", + "integrity": "sha512-wYU+/zlJWih7ZmonWVjGQ18tG7GboI9asMNjRBM5fpIFJWXSioQttCTw9qGL44cP82ghM8sCV9apEqm1zBDq2w==" + }, + "node_modules/@types/jasmine": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.8.tgz", + "integrity": "sha512-OJSUxLaxXsjjhob2DBzqzgrkLmukM3+JMpRp0r0E4HTdT1nwDCWhaswjYxazPij6uOdzHCJfNbDjmQ1/rnNbCg==", + "dev": true + }, + "node_modules/@types/jasminewd2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.3.tgz", + "integrity": "sha512-hYDVmQZT5VA2kigd4H4bv7vl/OhlympwREUemqBdOqtrYTo5Ytm12a5W5/nGgGYdanGVxj0x/VhZ7J3hOg/YKg==", + "dev": true, + "dependencies": { + "@types/jasmine": "*" + } + }, + "node_modules/@types/node": { + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", + "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==", + "dev": true + }, + "node_modules/@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "node_modules/@types/selenium-webdriver": { + "version": "2.53.43", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", + "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz", + "integrity": "sha512-49nwvW/Hx9i+OYHg+mRhKZfAlqThr11Dqz8TsrvqGKMhdI2ijy3KBJOun2Z4770TPjrIJhR6KxChQIDaz8clDA==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "debug": "^3.1.0", + "mamacro": "^0.0.3" + } + }, + "node_modules/@webassemblyjs/ast/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/ast/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz", + "integrity": "sha512-vrvvB18Kh4uyghSKb0NTv+2WZx871WL2NzwMj61jcq2bXkyhRC+8Q0oD7JGVf0+5i/fKQYQSBCNMMsDMRVAMqA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz", + "integrity": "sha512-dBh2CWYqjaDlvMmRP/kudxpdh30uXjIbpkLj9HQe+qtYlwvYjPRjdQXrq1cTAAOUSMTtzqbXIxEdEZmyKfcwsg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz", + "integrity": "sha512-v7igWf1mHcpJNbn4m7e77XOAWXCDT76Xe7Is1VQFXc4K5jRcFrl9D0NrqM4XifQ0bXiuTSkTKMYqDxu5MhNljA==", + "dev": true, + "dependencies": { + "debug": "^3.1.0" + } + }, + "node_modules/@webassemblyjs/helper-buffer/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/helper-buffer/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-code-frame": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz", + "integrity": "sha512-yN6ScQQDFCiAXnVctdVO/J5NQRbwyTbQzsGzEgXsAnrxhjp0xihh+nNHQTMrq5UhOqTb5LykpJAvEv9AT0jnAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/wast-printer": "1.5.13" + } + }, + "node_modules/@webassemblyjs/helper-fsm": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz", + "integrity": "sha512-hSIKzbXjVMRvy3Jzhgu+vDd/aswJ+UMEnLRCkZDdknZO3Z9e6rp1DAs0tdLItjCFqkz9+0BeOPK/mk3eYvVzZg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-module-context": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz", + "integrity": "sha512-zxJXULGPLB7r+k+wIlvGlXpT4CYppRz8fLUM/xobGHc9Z3T6qlmJD9ySJ2jknuktuuiR9AjnNpKYDECyaiX+QQ==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "mamacro": "^0.0.3" + } + }, + "node_modules/@webassemblyjs/helper-module-context/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/helper-module-context/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz", + "integrity": "sha512-0n3SoNGLvbJIZPhtMFq0XmmnA/YmQBXaZKQZcW8maGKwLpVcgjNrxpFZHEOLKjXJYVN5Il8vSfG7nRX50Zn+aw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz", + "integrity": "sha512-IJ/goicOZ5TT1axZFSnlAtz4m8KEjYr12BNOANAwGFPKXM4byEDaMNXYowHMG0yKV9a397eU/NlibFaLwr1fbw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "debug": "^3.1.0" + } + }, + "node_modules/@webassemblyjs/helper-wasm-section/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/helper-wasm-section/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz", + "integrity": "sha512-TseswvXEPpG5TCBKoLx9tT7+/GMACjC1ruo09j46ULRZWYm8XHpDWaosOjTnI7kr4SRJFzA6MWoUkAB+YCGKKg==", + "dev": true, + "dependencies": { + "ieee754": "^1.1.11" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.5.13.tgz", + "integrity": "sha512-0NRMxrL+GG3eISGZBmLBLAVjphbN8Si15s7jzThaw1UE9e5BY1oH49/+MA1xBzxpf1OW5sf9OrPDOclk9wj2yg==", + "dev": true, + "dependencies": { + "long": "4.0.0" + } + }, + "node_modules/@webassemblyjs/leb128/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.5.13.tgz", + "integrity": "sha512-Ve1ilU2N48Ew0lVGB8FqY7V7hXjaC4+PeZM+vDYxEd+R2iQ0q+Wb3Rw8v0Ri0+rxhoz6gVGsnQNb4FjRiEH/Ng==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz", + "integrity": "sha512-X7ZNW4+Hga4f2NmqENnHke2V/mGYK/xnybJSIXImt1ulxbCOEs/A+ZK/Km2jgihjyVxp/0z0hwIcxC6PrkWtgw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/helper-wasm-section": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "@webassemblyjs/wast-printer": "1.5.13", + "debug": "^3.1.0" + } + }, + "node_modules/@webassemblyjs/wasm-edit/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/wasm-edit/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz", + "integrity": "sha512-yfv94Se8R73zmr8GAYzezFHc3lDwE/lBXQddSiIZEKZFuqy7yWtm3KMwA1uGbv5G1WphimJxboXHR80IgX1hQA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz", + "integrity": "sha512-IkXSkgzVhQ0QYAdIayuCWMmXSYx0dHGU8Ah/AxJf1gBvstMWVnzJnBwLsXLyD87VSBIcsqkmZ28dVb0mOC3oBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "debug": "^3.1.0" + } + }, + "node_modules/@webassemblyjs/wasm-opt/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz", + "integrity": "sha512-XnYoIcu2iqq8/LrtmdnN3T+bRjqYFjRHqWbqK3osD/0r/Fcv4d9ecRzjVtC29ENEuNTK4mQ9yyxCBCbK8S/cpg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "node_modules/@webassemblyjs/wast-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz", + "integrity": "sha512-Lbz65T0LQ1LgzKiUytl34CwuhMNhaCLgrh0JW4rJBN6INnBB8NMwUfQM+FxTnLY9qJ+lHJL/gCM5xYhB9oWi4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/floating-point-hex-parser": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-code-frame": "1.5.13", + "@webassemblyjs/helper-fsm": "1.5.13", + "long": "^3.2.0", + "mamacro": "^0.0.3" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz", + "integrity": "sha512-QcwogrdqcBh8Z+eUF8SG+ag5iwQSXxQJELBEHmLkk790wgQgnIMmntT2sMAMw53GiFNckArf5X0bsCA44j3lWQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "long": "^3.2.0" + } + }, + "node_modules/abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "dev": true, + "dependencies": { + "acorn": "^5.0.0" + } + }, + "node_modules/adal-angular": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/adal-angular/-/adal-angular-1.0.17.tgz", + "integrity": "sha1-bpNuDkH5HTsqiOf/ypwvb29WLMQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/adm-zip": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", + "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=", + "dev": true, + "engines": { + "node": ">=0.3.0" + } + }, + "node_modules/after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "node_modules/agent-base": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "dev": true, + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true, + "peerDependencies": { + "ajv": ">=5.0.0" + } + }, + "node_modules/ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ajv/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "optional": true, + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/align-text/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/angular2-text-mask": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/angular2-text-mask/-/angular2-text-mask-9.0.0.tgz", + "integrity": "sha512-iALcnhJPS1zvX48d86rgUgDe/crX6XfhZrXC4Gdlo2/YwZW7u7KJZY6/b3ieSCIWVq/E6p+wDCzeo3E6leRjDA==", + "dependencies": { + "text-mask-core": "^5.0.0" + } + }, + "node_modules/ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/app-root-path": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", + "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "dependencies": { + "default-require-extensions": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "dev": true + }, + "node_modules/array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arraybuffer.slice": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", + "dev": true + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true, + "optional": true + }, + "node_modules/asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "node_modules/async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "node_modules/async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/atob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autoprefixer": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-8.6.5.tgz", + "integrity": "sha512-PLWJN3Xo/rycNkx+mp8iBDMTm3FeWe4VmYaZDSqL5QQB9sLsQkG5k8n+LNDFnhh9kdq2K+egL/icpctOmDHwig==", + "dev": true, + "dependencies": { + "browserslist": "^3.2.8", + "caniuse-lite": "^1.0.30000864", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^6.0.23", + "postcss-value-parser": "^3.2.3" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "node_modules/babel-code-frame/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "dependencies": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "node_modules/babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + } + }, + "node_modules/backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "node_modules/base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "dependencies": { + "callsite": "1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/blob": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", + "dev": true + }, + "node_modules/block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "optional": true, + "dependencies": { + "inherits": "~2.0.0" + }, + "engines": { + "node": "0.4 || >=0.5.8" + } + }, + "node_modules/blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "blocking-proxy": "built/lib/bin.js" + }, + "engines": { + "node": ">=6.9.x" + } + }, + "node_modules/blocking-proxy/node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "node_modules/bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "dependencies": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "node_modules/bootstrap": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.1.tgz", + "integrity": "sha512-SpiDSOcbg4J/PjVSt4ny5eY6j74VbVSjROY4Fb/WIUXBV9cnb5luyR4KnPvNoXuGnBK1T+nJIWqRsvU3yP8Mcg==", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jquery": "1.9.1 - 3", + "popper.js": "^1.14.3" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "dependencies": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" + }, + "bin": { + "browserslist": "cli.js" + } + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", + "dev": true + }, + "node_modules/buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "node_modules/builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "dependencies": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cachedir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.1.0.tgz", + "integrity": "sha512-xGBpPqoBvn3unBW7oxgb8aJn42K0m9m1/wyjmazah10Fq7bROGG3kRAE6OIyr3U3PIJUqGuebhCEdMk9OKJG0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "dependencies": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001035", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz", + "integrity": "sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ==", + "dev": true + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "optional": true, + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.2.2.tgz", + "integrity": "sha512-LvixLAQ4MYhbf7hgL4o5PeK32gJKvVzDRiSNIApDofQvyhl8adgG2lJVXn4+ekQoK7HL9RF8lqxwerpe0x2pCw==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.1.0", + "escape-string-regexp": "^1.0.5", + "supports-color": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "dependencies": { + "has-flag": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", + "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.0" + }, + "optionalDependencies": { + "fsevents": "^1.1.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/circular-dependency-plugin": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz", + "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "webpack": ">=4.0.1" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "optional": true, + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/cliui/node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dev": true, + "dependencies": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/codelyzer": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.2.1.tgz", + "integrity": "sha512-CKwfgpfkqi9dyzy4s6ELaxJ54QgJ6A8iTSsM4bzHbLuTpbKncvNc3DUlCvpnkHBhK47gEf4qFsWoYqLrJPhy6g==", + "dev": true, + "dependencies": { + "app-root-path": "^2.0.1", + "css-selector-tokenizer": "^0.7.0", + "cssauron": "^1.4.0", + "semver-dsl": "^1.0.1", + "source-map": "^0.5.6", + "sprintf-js": "^1.0.3" + }, + "peerDependencies": { + "@angular/common": ">=2.3.1 <7.0.0 || >6.0.0-beta <7.0.0", + "@angular/compiler": ">=2.3.1 <7.0.0 || >6.0.0-beta <7.0.0", + "@angular/core": ">=2.3.1 <7.0.0 || >6.0.0-beta <7.0.0", + "@angular/platform-browser": ">=2.3.1 <7.0.0 || >6.0.0-beta <7.0.0", + "@angular/platform-browser-dynamic": ">=2.3.1 <7.0.0 || >6.0.0-beta <7.0.0", + "tslint": "^5.0.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "dev": true, + "dependencies": { + "color-name": "1.1.1" + } + }, + "node_modules/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + }, + "node_modules/colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combine-lists": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", + "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", + "dev": true, + "dependencies": { + "lodash": "^4.5.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "node_modules/commitizen": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-3.0.4.tgz", + "integrity": "sha512-djR5F7RBsGALyUEm/B1H/85nsN4L1F5DhWN+9/efSwqHDSyhw2MK6MF2VRuD26PUqGkQbcUlYO61btkTWjcjVw==", + "dev": true, + "dependencies": { + "cachedir": "2.1.0", + "cz-conventional-changelog": "2.1.0", + "dedent": "0.7.0", + "detect-indent": "^5.0.0", + "find-node-modules": "1.0.4", + "find-root": "1.1.0", + "fs-extra": "^7.0.0", + "glob": "7.1.3", + "inquirer": "6.2.0", + "lodash": "4.17.11", + "minimist": "1.2.0", + "shelljs": "0.7.6", + "strip-json-comments": "2.0.1" + }, + "bin": { + "commitizen": "bin/commitizen", + "git-cz": "bin/git-cz" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commitizen/node_modules/detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/commitizen/node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, + "node_modules/commitizen/node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/commitizen/node_modules/lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "node_modules/commitizen/node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/compare-func": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", + "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" + } + }, + "node_modules/compare-versions": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.3.0.tgz", + "integrity": "sha512-MAAAIOdi2s4Gl6rZ76PNcUa9IOYB+5ICdT41o5uMRf09aEu/F9RK+qhe8RjXNPwcTjGV7KU7h2P/fljThFVqyQ==", + "dev": true + }, + "node_modules/component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "node_modules/component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect/node_modules/statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true, + "optional": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/conventional-changelog": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-1.1.24.tgz", + "integrity": "sha512-2WcSUst4Y3Z4hHvoMTWXMJr/DmgVdLiMOVY1Kak2LfFz+GIz2KDp5naqbFesYbfXPmaZ5p491dO0FWZIJoJw1Q==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^1.6.6", + "conventional-changelog-atom": "^0.2.8", + "conventional-changelog-codemirror": "^0.3.8", + "conventional-changelog-core": "^2.0.11", + "conventional-changelog-ember": "^0.3.12", + "conventional-changelog-eslint": "^1.0.9", + "conventional-changelog-express": "^0.3.6", + "conventional-changelog-jquery": "^0.1.0", + "conventional-changelog-jscs": "^0.1.0", + "conventional-changelog-jshint": "^0.3.8", + "conventional-changelog-preset-loader": "^1.1.8" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz", + "integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==", + "dev": true, + "dependencies": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + } + }, + "node_modules/conventional-changelog-angular/node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/conventional-changelog-atom": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-0.2.8.tgz", + "integrity": "sha512-8pPZqhMbrnltNBizjoDCb/Sz85KyUXNDQxuAEYAU5V/eHn0okMBVjqc8aHWYpHrytyZWvMGbayOlDv7i8kEf6g==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + } + }, + "node_modules/conventional-changelog-atom/node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/conventional-changelog-codemirror": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.3.8.tgz", + "integrity": "sha512-3HFZKtBXTaUCHvz7ai6nk2+psRIkldDoNzCsom0egDtVmPsvvHZkzjynhdQyULfacRSsBTaiQ0ol6nBOL4dDiQ==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + } + }, + "node_modules/conventional-changelog-codemirror/node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/conventional-changelog-core": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-2.0.11.tgz", + "integrity": "sha512-HvTE6RlqeEZ/NFPtQeFLsIDOLrGP3bXYr7lFLMhCVsbduF1MXIe8OODkwMFyo1i9ku9NWBwVnVn0jDmIFXjDRg==", + "dev": true, + "dependencies": { + "conventional-changelog-writer": "^3.0.9", + "conventional-commits-parser": "^2.1.7", + "dateformat": "^3.0.0", + "get-pkg-repo": "^1.0.0", + "git-raw-commits": "^1.3.6", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^1.3.6", + "lodash": "^4.2.1", + "normalize-package-data": "^2.3.5", + "q": "^1.5.1", + "read-pkg": "^1.1.0", + "read-pkg-up": "^1.0.1", + "through2": "^2.0.0" + } + }, + "node_modules/conventional-changelog-core/node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/conventional-changelog-ember": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-0.3.12.tgz", + "integrity": "sha512-mmJzA7uzbrOqeF89dMMi6z17O07ORTXlTMArnLG9ZTX4oLaKNolUlxFUFlFm9JUoVWajVpaHQWjxH1EOQ+ARoQ==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + } + }, + "node_modules/conventional-changelog-ember/node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/conventional-changelog-eslint": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-1.0.9.tgz", + "integrity": "sha512-h87nfVh2fdk9fJIvz26wCBsbDC/KxqCc5wSlNMZbXcARtbgNbNDIF7Y7ctokFdnxkzVdaHsbINkh548T9eBA7Q==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + } + }, + "node_modules/conventional-changelog-eslint/node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/conventional-changelog-express": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-0.3.6.tgz", + "integrity": "sha512-3iWVtBJZ9RnRnZveNDzOD8QRn6g6vUif0qVTWWyi5nUIAbuN1FfPVyKdAlJJfp5Im+dE8Kiy/d2SpaX/0X678Q==", + "dev": true, + "dependencies": { + "q": "^1.5.1" + } + }, + "node_modules/conventional-changelog-express/node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/conventional-changelog-jquery": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-0.1.0.tgz", + "integrity": "sha1-Agg5cWLjhGmG5xJztsecW1+A9RA=", + "dev": true, + "dependencies": { + "q": "^1.4.1" + } + }, + "node_modules/conventional-changelog-jscs": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-jscs/-/conventional-changelog-jscs-0.1.0.tgz", + "integrity": "sha1-BHnrRDzH1yxYvwvPDvHURKkvDlw=", + "dev": true, + "dependencies": { + "q": "^1.4.1" + } + }, + "node_modules/conventional-changelog-jshint": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-0.3.8.tgz", + "integrity": "sha512-hn9QU4ZI/5V50wKPJNPGT4gEWgiBFpV6adieILW4MaUFynuDYOvQ71EMSj3EznJyKi/KzuXpc9dGmX8njZMjig==", + "dev": true, + "dependencies": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + } + }, + "node_modules/conventional-changelog-jshint/node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/conventional-changelog-preset-loader": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-1.1.8.tgz", + "integrity": "sha512-MkksM4G4YdrMlT2MbTsV2F6LXu/hZR0Tc/yenRrDIKRwBl/SP7ER4ZDlglqJsCzLJi4UonBc52Bkm5hzrOVCcw==", + "dev": true + }, + "node_modules/conventional-changelog-writer": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-3.0.9.tgz", + "integrity": "sha512-n9KbsxlJxRQsUnK6wIBRnARacvNnN4C/nxnxCkH+B/R1JS2Fa+DiP1dU4I59mEDEjgnFaN2+9wr1P1s7GYB5/Q==", + "dev": true, + "dependencies": { + "compare-func": "^1.3.1", + "conventional-commits-filter": "^1.1.6", + "dateformat": "^3.0.0", + "handlebars": "^4.0.2", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "semver": "^5.5.0", + "split": "^1.0.0", + "through2": "^2.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.js" + } + }, + "node_modules/conventional-changelog-writer/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "dependencies": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/conventional-changelog-writer/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "dependencies": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-changelog-writer/node_modules/trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commit-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-2.2.0.tgz", + "integrity": "sha1-XblXOdbCEqy+e29lahG5QLqmiUY=", + "dev": true + }, + "node_modules/conventional-commits-filter": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz", + "integrity": "sha512-KcDgtCRKJCQhyk6VLT7zR+ZOyCnerfemE/CsR3iQpzRRFbLEs0Y6rwk3mpDvtOh04X223z+1xyJ582Stfct/0Q==", + "dev": true, + "dependencies": { + "is-subset": "^0.1.1", + "modify-values": "^1.0.0" + } + }, + "node_modules/conventional-commits-parser": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz", + "integrity": "sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ==", + "dev": true, + "dependencies": { + "is-text-path": "^1.0.0", + "JSONStream": "^1.0.4", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0", + "trim-off-newlines": "^1.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.js" + } + }, + "node_modules/conventional-commits-parser/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "dependencies": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/conventional-commits-parser/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "dependencies": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-commits-parser/node_modules/trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/conventional-recommended-bump": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-1.2.1.tgz", + "integrity": "sha512-oJjG6DkRgtnr/t/VrPdzmf4XZv8c4xKVJrVT4zrSHd92KEL+EYxSbYoKq8lQ7U5yLMw7130wrcQTLRjM/T+d4w==", + "dev": true, + "dependencies": { + "concat-stream": "^1.4.10", + "conventional-commits-filter": "^1.1.1", + "conventional-commits-parser": "^2.1.1", + "git-raw-commits": "^1.3.0", + "git-semver-tags": "^1.3.0", + "meow": "^3.3.0", + "object-assign": "^4.0.1" + }, + "bin": { + "conventional-recommended-bump": "cli.js" + } + }, + "node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "node_modules/copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "dependencies": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.2.tgz", + "integrity": "sha512-zmC33E8FFSq3AbflTvqvPvBo621H36Afsxlui91d+QyZxPIuXghfnTsa1CuqiAaCPgJoSUWfTFbKJnadZpKEbQ==", + "dev": true, + "dependencies": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js." + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cosmiconfig/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "dev": true + }, + "node_modules/css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "dependencies": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "node_modules/css-selector-tokenizer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "dev": true, + "dependencies": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + } + }, + "node_modules/css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/cssauron": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true, + "dependencies": { + "through": "X.X.X" + } + }, + "node_modules/cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + } + }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", + "dev": true + }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "node_modules/cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true + }, + "node_modules/cz-conventional-changelog": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-2.1.0.tgz", + "integrity": "sha1-L0vHOQ4yROTfKT5ro1Hkx0Cnx2Q=", + "dev": true, + "dependencies": { + "conventional-commit-types": "^2.0.0", + "lodash.map": "^4.5.1", + "longest": "^1.0.1", + "right-pad": "^1.0.1", + "word-wrap": "^1.0.3" + } + }, + "node_modules/dargs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", + "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "node_modules/deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "dependencies": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/default-gateway": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz", + "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", + "dev": true, + "os": [ + "android", + "darwin", + "freebsd", + "linux", + "openbsd", + "sunos", + "win32" + ], + "dependencies": { + "execa": "^0.10.0", + "ip-regex": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/default-gateway/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/default-gateway/node_modules/execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "dependencies": { + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/default-require-extensions/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "dependencies": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/del/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "node_modules/detect-file": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz", + "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", + "dev": true, + "dependencies": { + "fs-exists-sync": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "dependencies": { + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "node_modules/dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "dependencies": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "dependencies": { + "buffer-indexof": "^1.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true, + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "dev": true, + "dependencies": { + "is-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dotgitignore": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-1.0.3.tgz", + "integrity": "sha512-eu5XjSstm0WXQsARgo6kPjkINYZlOUW+z/KtAAIBjHa5mUpMPrxJytbPIndWz6GubBuuuH5ljtVcXKnVnH5q8w==", + "dev": true, + "dependencies": { + "find-up": "^2.1.0", + "minimatch": "^3.0.4" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "dependencies": { + "jsbn": "~0.1.0" + } + }, + "node_modules/edge-launcher": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/edge-launcher/-/edge-launcher-1.2.2.tgz", + "integrity": "sha1-60Cq+9Bnpup27/+rBke81VCbN7I=", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.376.tgz", + "integrity": "sha512-cv/PYVz5szeMz192ngilmezyPNFkUjuynuL2vNdiqIrio440nfTDdc0JJU0TS2KHLSVCs9gBbt4CFqM+HcBnjw==", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "dev": true, + "dependencies": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "optional": true + }, + "node_modules/emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.3.tgz", + "integrity": "sha1-jef5eJXSDTm4X4ju7nd7K9QrE9Q=", + "dev": true, + "dependencies": { + "accepts": "1.3.3", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "2.3.3", + "engine.io-parser": "1.3.2", + "ws": "1.1.2" + } + }, + "node_modules/engine.io-client": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.3.tgz", + "integrity": "sha1-F5jtk0USRkU9TG9jXXogH+lA1as=", + "dev": true, + "dependencies": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "2.3.3", + "engine.io-parser": "1.3.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parsejson": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "1.1.2", + "xmlhttprequest-ssl": "1.5.3", + "yeast": "0.1.2" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "dependencies": { + "ms": "0.7.2" + } + }, + "node_modules/engine.io-client/node_modules/ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "node_modules/engine.io-parser": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz", + "integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=", + "dev": true, + "dependencies": { + "after": "0.8.2", + "arraybuffer.slice": "0.0.6", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.4", + "has-binary": "0.1.7", + "wtf-8": "1.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "dev": true, + "dependencies": { + "mime-types": "~2.1.11", + "negotiator": "0.6.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "dependencies": { + "ms": "0.7.2" + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/enhanced-resolve/node_modules/memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "node_modules/entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "dependencies": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.12.0" + }, + "optionalDependencies": { + "source-map": "~0.2.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "dependencies": { + "estraverse": "^4.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "dev": true + }, + "node_modules/events": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "dependencies": { + "original": "^1.0.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-braces": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", + "dev": true, + "dependencies": { + "array-slice": "^0.2.3", + "array-unique": "^0.2.1", + "braces": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-braces/node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-braces/node_modules/braces": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", + "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", + "dev": true, + "dependencies": { + "expand-range": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-braces/node_modules/expand-range": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", + "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", + "dev": true, + "dependencies": { + "is-number": "^0.1.1", + "repeat-string": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-braces/node_modules/is-number": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-braces/node_modules/repeat-string": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", + "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "dependencies": { + "fill-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "dependencies": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "node_modules/express/node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/express/node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/express/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "node_modules/express/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "dependencies": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fastparse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", + "dev": true + }, + "node_modules/faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-loader": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "dev": true, + "dependencies": { + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" + }, + "engines": { + "node": ">= 4.3 < 5.0.0 || >= 5.10" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/file-saver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.0.tgz", + "integrity": "sha512-cYM1ic5DAkg25pHKgi5f10ziAM7RJU37gaH1XQlyNDrtUnzhC/dfoV9zf2OmF0RMKi42jG5B0JWBnPQqyj/G6g==" + }, + "node_modules/filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "dependencies": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/find-node-modules": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/find-node-modules/-/find-node-modules-1.0.4.tgz", + "integrity": "sha1-tt6zzMtpnIcDdne87eLF9YYrJVA=", + "dev": true, + "dependencies": { + "findup-sync": "0.4.2", + "merge": "^1.2.0" + } + }, + "node_modules/find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/findup": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz", + "integrity": "sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=", + "dev": true, + "dependencies": { + "colors": "~0.6.0-1", + "commander": "~2.1.0" + }, + "bin": { + "findup": "bin/findup.js" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/findup-sync": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.4.2.tgz", + "integrity": "sha1-qBF9D3MST1pFRoOVef5S1xKfteU=", + "dev": true, + "dependencies": { + "detect-file": "^0.1.0", + "is-glob": "^2.0.1", + "micromatch": "^2.3.7", + "resolve-dir": "^0.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/findup-sync/node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup/node_modules/colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/findup/node_modules/commander": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", + "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=", + "dev": true, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/follow-redirects": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.0.tgz", + "integrity": "sha512-fdrt472/9qQ6Kgjvb935ig6vJCuofpBUD14f9Vb+SLlm7xIe4Qva5gey8EKtv8lp7ahE1wilg3xL1znpVGtZIA==", + "dev": true, + "dependencies": { + "debug": "^3.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/follow-redirects/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true, + "dependencies": { + "null-check": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-extra": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz", + "integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "bundleDependencies": [ + "node-pre-gyp" + ], + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fsevents/node_modules/abbrev": { + "version": "1.1.1", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/ansi-regex": { + "version": "2.1.1", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/aproba": { + "version": "1.2.0", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/are-we-there-yet": { + "version": "1.1.4", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/fsevents/node_modules/balanced-match": { + "version": "1.0.0", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/brace-expansion": { + "version": "1.1.11", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fsevents/node_modules/chownr": { + "version": "1.0.1", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/code-point-at": { + "version": "1.1.0", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/concat-map": { + "version": "0.0.1", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/console-control-strings": { + "version": "1.1.0", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/core-util-is": { + "version": "1.0.2", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/debug": { + "version": "2.6.9", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/fsevents/node_modules/deep-extend": { + "version": "0.5.1", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/delegates": { + "version": "1.0.0", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/detect-libc": { + "version": "1.0.3", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "inBundle": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/fsevents/node_modules/fs-minipass": { + "version": "1.2.5", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minipass": "^2.2.1" + } + }, + "node_modules/fsevents/node_modules/fs.realpath": { + "version": "1.0.0", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/gauge": { + "version": "2.7.4", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/fsevents/node_modules/glob": { + "version": "7.1.2", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/has-unicode": { + "version": "2.0.1", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/iconv-lite": { + "version": "0.4.21", + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "safer-buffer": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/ignore-walk": { + "version": "3.0.1", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/fsevents/node_modules/inflight": { + "version": "1.0.6", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/fsevents/node_modules/inherits": { + "version": "2.0.3", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/ini": { + "version": "1.3.5", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/isarray": { + "version": "1.0.0", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/minimatch": { + "version": "3.0.4", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/minimist": { + "version": "0.0.8", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/minipass": { + "version": "2.2.4", + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "node_modules/fsevents/node_modules/minizlib": { + "version": "1.1.0", + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minipass": "^2.2.1" + } + }, + "node_modules/fsevents/node_modules/mkdirp": { + "version": "0.5.1", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/fsevents/node_modules/ms": { + "version": "2.0.0", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/needle": { + "version": "2.2.0", + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 0.10.x" + } + }, + "node_modules/fsevents/node_modules/node-pre-gyp": { + "version": "0.10.0", + "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", + "deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/fsevents/node_modules/nopt": { + "version": "4.0.1", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/fsevents/node_modules/npm-bundled": { + "version": "1.0.3", + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/npm-packlist": { + "version": "1.1.10", + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "node_modules/fsevents/node_modules/npmlog": { + "version": "4.1.2", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/fsevents/node_modules/number-is-nan": { + "version": "1.0.1", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/object-assign": { + "version": "4.1.1", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/once": { + "version": "1.4.0", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/fsevents/node_modules/os-homedir": { + "version": "1.0.2", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/os-tmpdir": { + "version": "1.0.2", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/osenv": { + "version": "0.1.5", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/fsevents/node_modules/path-is-absolute": { + "version": "1.0.1", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/process-nextick-args": { + "version": "2.0.0", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/rc": { + "version": "1.2.7", + "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/fsevents/node_modules/rc/node_modules/minimist": { + "version": "1.2.0", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/readable-stream": { + "version": "2.3.6", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/fsevents/node_modules/rimraf": { + "version": "2.6.2", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "glob": "^7.0.5" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/fsevents/node_modules/safe-buffer": { + "version": "5.1.1", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/safer-buffer": { + "version": "2.1.2", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/sax": { + "version": "1.2.4", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/semver": { + "version": "5.5.0", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true, + "inBundle": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/fsevents/node_modules/set-blocking": { + "version": "2.0.0", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/signal-exit": { + "version": "3.0.2", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/string_decoder": { + "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fsevents/node_modules/string-width": { + "version": "1.0.2", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/strip-ansi": { + "version": "3.0.1", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/strip-json-comments": { + "version": "2.0.1", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/tar": { + "version": "4.4.1", + "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/fsevents/node_modules/util-deprecate": { + "version": "1.0.2", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/wide-align": { + "version": "1.1.2", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "string-width": "^1.0.2" + } + }, + "node_modules/fsevents/node_modules/wrappy": { + "version": "1.0.2", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/yallist": { + "version": "3.0.2", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "optional": true, + "dependencies": { + "globule": "^1.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "node_modules/get-pkg-repo": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz", + "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "meow": "^3.3.0", + "normalize-package-data": "^2.3.0", + "parse-github-repo-url": "^1.3.0", + "through2": "^2.0.0" + }, + "bin": { + "get-pkg-repo": "cli.js" + } + }, + "node_modules/get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/git-describe": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/git-describe/-/git-describe-4.0.3.tgz", + "integrity": "sha1-2BSCqK2nodgeBg1DisjhG0o5AzY=", + "dependencies": { + "lodash": "^4.16.6" + }, + "engines": { + "node": ">=0.12.17" + }, + "optionalDependencies": { + "semver": "^5.3.0" + } + }, + "node_modules/git-raw-commits": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-1.3.6.tgz", + "integrity": "sha512-svsK26tQ8vEKnMshTDatSIQSMDdz8CxIIqKsvPqbtV23Etmw6VNaFAitu8zwZ0VrOne7FztwPyRLxK7/DIUTQg==", + "dev": true, + "dependencies": { + "dargs": "^4.0.1", + "lodash.template": "^4.0.2", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0" + }, + "bin": { + "git-raw-commits": "cli.js" + } + }, + "node_modules/git-raw-commits/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "dependencies": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/git-raw-commits/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "dependencies": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-raw-commits/node_modules/trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "dev": true, + "dependencies": { + "gitconfiglocal": "^1.0.0", + "pify": "^2.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-remote-origin-url/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/git-semver-tags": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-1.3.6.tgz", + "integrity": "sha512-2jHlJnln4D/ECk9FxGEBh3k44wgYdWjWDtMmJPaecjoRmxKo3Y1Lh8GMYuOPu04CHw86NTAODchYjC5pnpMQig==", + "dev": true, + "dependencies": { + "meow": "^4.0.0", + "semver": "^5.5.0" + }, + "bin": { + "git-semver-tags": "cli.js" + } + }, + "node_modules/git-semver-tags/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "dependencies": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/git-semver-tags/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "dependencies": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/git-semver-tags/node_modules/trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", + "dev": true, + "dependencies": { + "ini": "^1.3.2" + } + }, + "node_modules/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "dependencies": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-base/node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/glob-base/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-base/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", + "dev": true, + "dependencies": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-modules/node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "optional": true, + "dependencies": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + }, + "node_modules/handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "dev": true, + "dependencies": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^2.6" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/handlebars/node_modules/uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "optional": true, + "dependencies": { + "source-map": "~0.5.1", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + } + }, + "node_modules/handlebars/node_modules/uglify-js/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-binary": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/has-binary/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true, + "engines": [ + "node >= 0.4.0" + ] + }, + "node_modules/html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "dependencies": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/html-minifier/node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "node_modules/html-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "deprecated": "3.x is no longer supported", + "dev": true, + "dependencies": { + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", + "util.promisify": "1.0.0" + }, + "engines": { + "node": ">=6.9" + }, + "peerDependencies": { + "webpack": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/html-webpack-plugin/node_modules/loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "dependencies": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "node_modules/htmlparser2/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-parser-js": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "dependencies": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", + "dev": true, + "dependencies": { + "http-proxy": "^1.16.2", + "is-glob": "^4.0.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "dependencies": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "node_modules/iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "node_modules/ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "node_modules/immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", + "dev": true + }, + "node_modules/import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "dependencies": { + "import-from": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "dependencies": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/in-publish": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", + "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==", + "dev": true, + "optional": true, + "bin": { + "in-install": "in-install.js", + "in-publish": "in-publish.js", + "not-in-install": "not-in-install.js", + "not-in-publish": "not-in-publish.js" + } + }, + "node_modules/indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/inquirer": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/internal-ip": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz", + "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", + "dev": true, + "os": [ + "android", + "darwin", + "freebsd", + "linux", + "openbsd", + "sunos", + "win32" + ], + "dependencies": { + "default-gateway": "^2.6.0", + "ipaddr.js": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "node_modules/ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "dependencies": { + "builtin-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "dependencies": { + "is-primitive": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-odd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", + "dev": true, + "dependencies": { + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-odd/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, + "node_modules/is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "dev": true, + "dependencies": { + "text-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", + "dev": true, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "deprecated": "This module is no longer maintained, try this instead:\n npm i nyc\nVisit https://istanbul.js.org/integrations for other alternatives.", + "dev": true, + "dependencies": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "istanbul": "lib/cli.js" + } + }, + "node_modules/istanbul-api": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.1.tgz", + "integrity": "sha512-duj6AlLcsWNwUpfyfHt0nWIeRiZpuShnP40YTxOGQgtaN8fd6JYSxsvxUphTDy8V5MfDXo4s/xVCIIvVCO808g==", + "dev": true, + "dependencies": { + "async": "^2.1.4", + "compare-versions": "^3.1.0", + "fileset": "^2.0.2", + "istanbul-lib-coverage": "^1.2.0", + "istanbul-lib-hook": "^1.2.0", + "istanbul-lib-instrument": "^1.10.1", + "istanbul-lib-report": "^1.1.4", + "istanbul-lib-source-maps": "^1.2.4", + "istanbul-reports": "^1.3.0", + "js-yaml": "^3.7.0", + "mkdirp": "^0.5.1", + "once": "^1.4.0" + } + }, + "node_modules/istanbul-api/node_modules/async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.10" + } + }, + "node_modules/istanbul-instrumenter-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", + "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", + "dev": true, + "dependencies": { + "convert-source-map": "^1.5.0", + "istanbul-lib-instrument": "^1.7.3", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0" + }, + "engines": { + "node": ">= 4.8 < 5.0.0 || >= 5.10" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/istanbul-instrumenter-loader/node_modules/ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "dependencies": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "node_modules/istanbul-instrumenter-loader/node_modules/schema-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true, + "dependencies": { + "ajv": "^5.0.0" + }, + "engines": { + "node": ">= 4.3 < 5.0.0 || >= 5.10" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", + "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", + "dev": true + }, + "node_modules/istanbul-lib-hook": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.1.tgz", + "integrity": "sha512-eLAMkPG9FU0v5L02lIkcj/2/Zlz9OuluaXikdr5iStk8FDbSwAixTK9TkYxbF0eNnzAJTwM2fkV2A1tpsIp4Jg==", + "deprecated": "1.2.0 should have been a major version bump", + "dev": true, + "dependencies": { + "append-transform": "^1.0.0" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", + "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", + "dev": true, + "dependencies": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.0", + "semver": "^5.3.0" + } + }, + "node_modules/istanbul-lib-report": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz", + "integrity": "sha512-Azqvq5tT0U09nrncK3q82e/Zjkxa4tkFZv7E6VcqP0QCPn6oNljDPfrZEC/umNXds2t7b8sRJfs6Kmpzt8m2kA==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.5.tgz", + "integrity": "sha512-8O2T/3VhrQHn0XcJbP1/GN7kXMiRAlPi+fj3uEHrjBD8Oz7Py0prSC25C09NuAZS6bgW1NNKAvCSHZXB0irSGA==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/istanbul-reports": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.3.0.tgz", + "integrity": "sha512-y2Z2IMqE1gefWUaVjrBm0mSKvUkaBy9Vqz8iwr/r40Y9hBbIteH5wqHG/9DLTfJ9xUnUT2j7A3+VVJ6EaYBllA==", + "dev": true, + "dependencies": { + "handlebars": "^4.0.3" + } + }, + "node_modules/istanbul/node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/istanbul/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul/node_modules/resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "node_modules/istanbul/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "dependencies": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/jasmine-core": { + "version": "2.99.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", + "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", + "dev": true + }, + "node_modules/jasmine-spec-reporter": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", + "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", + "dev": true, + "dependencies": { + "colors": "1.1.2" + } + }, + "node_modules/jasmine/node_modules/jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + }, + "node_modules/jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true, + "engines": { + "node": ">= 6.9.x" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT", + "peer": true + }, + "node_modules/js-base64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", + "dev": true, + "optional": true + }, + "node_modules/js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "node_modules/jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "deprecated": "Please use the native JSON object instead of JSON 3", + "dev": true + }, + "node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/jszip": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", + "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", + "dev": true, + "dependencies": { + "core-js": "~2.3.0", + "es6-promise": "~3.0.2", + "lie": "~3.1.0", + "pako": "~1.0.2", + "readable-stream": "~2.0.6" + } + }, + "node_modules/jszip/node_modules/core-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true + }, + "node_modules/jszip/node_modules/es6-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", + "dev": true + }, + "node_modules/jszip/node_modules/process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/karma": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz", + "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==", + "dev": true, + "dependencies": { + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "chokidar": "^1.4.1", + "colors": "^1.1.0", + "combine-lists": "^1.0.0", + "connect": "^3.6.0", + "core-js": "^2.2.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "expand-braces": "^0.1.1", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^3.8.0", + "log4js": "^0.6.31", + "mime": "^1.3.4", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", + "socket.io": "1.7.3", + "source-map": "^0.5.3", + "tmp": "0.0.31", + "useragent": "^2.1.12" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": "0.10 || 0.12 || 4 || 5 || 6 || 7 || 8" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, + "dependencies": { + "fs-access": "^1.0.0", + "which": "^1.2.1" + } + }, + "node_modules/karma-coverage-istanbul-reporter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.0.1.tgz", + "integrity": "sha512-UcgrHkFehI5+ivMouD8NH/UOHiX4oCAtwaANylzPFdcAuD52fnCUuelacq2gh8tZ4ydhU3+xiXofSq7j5Ehygw==", + "dev": true, + "dependencies": { + "istanbul-api": "^1.3.1", + "minimatch": "^3.0.4" + } + }, + "node_modules/karma-edge-launcher": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/karma-edge-launcher/-/karma-edge-launcher-0.4.2.tgz", + "integrity": "sha512-YAJZb1fmRcxNhMIWYsjLuxwODBjh2cSHgTW/jkVmdpGguJjLbs9ZgIK/tEJsMQcBLUkO+yO4LBbqYxqgGW2HIw==", + "dev": true, + "dependencies": { + "edge-launcher": "1.2.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-firefox-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz", + "integrity": "sha512-LbZ5/XlIXLeQ3cqnCbYLn+rOVhuMIK9aZwlP6eOLGzWdo1UVp7t6CN3DP4SafiRLjexKwHeKHDm0c38Mtd3VxA==", + "dev": true + }, + "node_modules/karma-ie-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz", + "integrity": "sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw=", + "dev": true, + "dependencies": { + "lodash": "^4.6.1" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-jasmine": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz", + "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=", + "dev": true, + "engines": { + "node": ">= 4" + }, + "peerDependencies": { + "jasmine-core": "*", + "karma": "*" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz", + "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=", + "dev": true, + "dependencies": { + "karma-jasmine": "^1.0.2" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-junit-reporter": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz", + "integrity": "sha1-T5xAzt+xo5X4rvh2q/lhiZF8Y5Y=", + "dev": true, + "dependencies": { + "path-is-absolute": "^1.0.0", + "xmlbuilder": "8.2.2" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-junit-reporter/node_modules/xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/karma-source-map-support": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.3.0.tgz", + "integrity": "sha512-HcPqdAusNez/ywa+biN4EphGz62MmQyPggUsDfsHqa7tSe4jdsxgvTKuDfIazjL+IOxpVWyT7Pr4dhAV+sxX5Q==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "node_modules/karma-spec-reporter": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.36.tgz", + "integrity": "sha512-11bvOl1x6ryKZph7kmbmMpbi8vsngEGxGOoeTlIcDaH3ab3j8aPJnZ+r+K/SS0sBSGy5VGkGYO2+hLct7hw/6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "1.4.0" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-spec-reporter/node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/karma/node_modules/anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "dependencies": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "node_modules/karma/node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "dependencies": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + }, + "optionalDependencies": { + "fsevents": "^1.0.0" + } + }, + "node_modules/karma/node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/karma/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "node_modules/karma/node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/less": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/less/-/less-3.8.1.tgz", + "integrity": "sha512-8HFGuWmL3FhQR0aH89escFNBQH/nEiYPP2ltDFdQw2chE28Yx2E3lhAIq9Y2saYwLSwa699s4dBVEfCY8Drf7Q==", + "dev": true, + "dependencies": { + "clone": "^2.1.2" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=4" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz", + "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">= 4.8 < 5.0.0 || >= 5.10" + }, + "peerDependencies": { + "less": "^2.3.1 || ^3.0.0", + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-1.5.0.tgz", + "integrity": "sha512-Of/H79rZqm2aeg4RnP9SMSh19qkKemoLT5VaJV58uH5AxeYWEcBgGFs753JEJ/Hm6BPvQVfIlrrjoBwYj8p7Tw==", + "dev": true, + "dependencies": { + "ejs": "^2.5.7" + }, + "peerDependencies": { + "webpack-sources": ">=1.0.0" + } + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "dependencies": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "node_modules/lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, + "node_modules/lodash.template": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", + "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "~3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", + "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "~3.0.0" + } + }, + "node_modules/log4js": { + "version": "0.6.38", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", + "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", + "dev": true, + "dependencies": { + "readable-stream": "~1.0.2", + "semver": "~4.3.3" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/log4js/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/log4js/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/log4js/node_modules/semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/log4js/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/loglevel": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", + "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-loglevel?utm_medium=referral&utm_source=npm_fund" + } + }, + "node_modules/long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "dependencies": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "node_modules/lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "dev": true + }, + "node_modules/mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, + "node_modules/map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "dependencies": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/meow/node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/merge": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz", + "integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo=", + "dev": true + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/microsoft-adal-angular6": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/microsoft-adal-angular6/-/microsoft-adal-angular6-1.0.0.tgz", + "integrity": "sha512-AU5/WfeHfyb/YhSjewaS/Cqr/SttV1msg33yReH/7B3EtBwlI3+logvo3ez6wJe37D/TYHtxXaoHsX8fYQOcxg==", + "dependencies": { + "@types/adal": "^1.0.29", + "adal-angular": "^1.0.15", + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/common": "^6.0.0-rc.0 || ^6.0.0", + "@angular/core": "^6.0.0-rc.0 || ^6.0.0" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.4.tgz", + "integrity": "sha512-o+Jm+ocb0asEngdM6FsZWtZsRzA8koFUudIDwYUfl94M3PejPHG7Vopw5hN9V8WsMkSFpm3tZP3Fesz89EyrfQ==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "engines": { + "node": ">= 6.9.0 <7.0.0 || >= 8.9.0" + }, + "peerDependencies": { + "webpack": "^4.4.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "node_modules/minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "dependencies": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "deprecated": "Critical bug fixed in v2.0.1, please upgrade to the latest version.", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "dependencies": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object/node_modules/for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "dependencies": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "dependencies": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "node_modules/nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "dev": true, + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", + "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-odd": "^2.0.0", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "node_modules/ng-bullet": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ng-bullet/-/ng-bullet-1.0.3.tgz", + "integrity": "sha512-qacsE/w/pLlBxebort1rkrE2B4Vc3idutcpe7tYiHVarz0V6Q5SN8E3d6NUp4UFBMOucpHlCpaASp7qEOsxM1Q==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "peerDependencies": { + "@angular/common": ">=5.0.0", + "@angular/core": ">=5.0.0", + "jasmine-core": ">=2.5.0" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/node-forge": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", + "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "dev": true, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node_modules/node-libs-browser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "node_modules/npm-registry-client": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.5.1.tgz", + "integrity": "sha512-7rjGF2eA7hKDidGyEWmHTiKfXkbrcQAsGL/Rh4Rt3x3YNRNHhwaTzVJfW3aNvvlhg4G62VCluif0sLCb/i51Hg==", + "dev": true, + "dependencies": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + }, + "optionalDependencies": { + "npmlog": "2 || ^3.1.0 || ^4.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "dependencies": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit/node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "dependencies": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "node_modules/optimist/node_modules/wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "dependencies": { + "url-parse": "^1.4.3" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + }, + "node_modules/parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "dependencies": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, + "dependencies": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-github-repo-url": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", + "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=", + "dev": true + }, + "node_modules/parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "dependencies": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-glob/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-glob/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "node_modules/parsejson": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", + "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", + "dev": true, + "dependencies": { + "better-assert": "~1.0.0" + } + }, + "node_modules/parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "dependencies": { + "better-assert": "~1.0.0" + } + }, + "node_modules/parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "dependencies": { + "better-assert": "~1.0.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "dependencies": { + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/portfinder": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.19.tgz", + "integrity": "sha512-23aeQKW9KgHe6citUrG3r9HjeX6vls0h713TAa+CwTKZwNIr/pD2ApaxYF4Um3ZZyq4ar+Siv3+fhoHaIwSOSw==", + "dev": true, + "dependencies": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-import": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-11.1.0.tgz", + "integrity": "sha512-5l327iI75POonjxkXgdRCUS+AlzAdBx4pOvMEhTKTCjb1p8IEeVR9yx3cPbmN7LIWJLbfnIXxAhoB4jpD0c/Cw==", + "dev": true, + "dependencies": { + "postcss": "^6.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "node_modules/postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "dev": true, + "dependencies": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/postcss-loader": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.6.tgz", + "integrity": "sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "postcss": "^6.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^0.4.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/postcss-url": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-7.3.2.tgz", + "integrity": "sha512-QMV5mA+pCYZQcUEPQkmor9vcPQ2MT+Ipuu8qdi1gVxbNiIiErEGft+eny1ak19qALoBkccS5AHaCaCDzh7b9MA==", + "dev": true, + "dependencies": { + "mime": "^1.4.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.0", + "postcss": "^6.0.1", + "xxhashjs": "^0.2.1" + } + }, + "node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "dependencies": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "optional": true, + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "node_modules/protractor": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.3.2.tgz", + "integrity": "sha512-pw4uwwiy5lHZjIguxNpkEwJJa7hVz+bJsvaTI+IbXlfn2qXwzbF8eghW/RmrZwE2sGx82I8etb8lVjQ+JrjejA==", + "dev": true, + "dependencies": { + "@types/node": "^6.0.46", + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "~2.53.39", + "blocking-proxy": "^1.0.0", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "optimist": "~0.6.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "^1.0.0", + "webdriver-manager": "^12.0.6" + }, + "bin": { + "protractor": "bin/protractor", + "webdriver-manager": "bin/webdriver-manager" + }, + "engines": { + "node": ">=6.9.x" + } + }, + "node_modules/protractor/node_modules/@types/node": { + "version": "6.0.112", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.112.tgz", + "integrity": "sha512-HnekQWq9t3Gl5aBYYi8sGyOWm85M5ub2llMrpQkRY94eJEUhsUr8qYNaeefv22cxxm+D67a+5zIzpl+dpFxdjQ==", + "dev": true + }, + "node_modules/protractor/node_modules/adm-zip": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz", + "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==", + "dev": true, + "engines": { + "node": ">=0.3.0" + } + }, + "node_modules/protractor/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "dependencies": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/protractor/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "dependencies": { + "source-map": "^0.5.6" + } + }, + "node_modules/protractor/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/protractor/node_modules/webdriver-manager": { + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.0.6.tgz", + "integrity": "sha1-PfGkgZdwELTL+MnYXHpXeCjA5ws=", + "dev": true, + "dependencies": { + "adm-zip": "^0.4.7", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.78.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + }, + "bin": { + "webdriver-manager": "bin/webdriver-manager" + }, + "engines": { + "node": ">=6.9.x" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dev": true, + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.5.0.tgz", + "integrity": "sha512-eELwFtFxL+uhmg4jPZOZXzSrPEYy4CaYQNbcchBbfxY+KjMpnv6XGf/aYWaQG49OTpfi2/DMziXtDM8XuJgoUA==", + "deprecated": "Version no longer supported. Upgrade to @latest", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "debug": "^3.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^2.2.1", + "mime": "^2.0.3", + "progress": "^2.0.0", + "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.1", + "ws": "^5.1.1" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/puppeteer/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/puppeteer/node_modules/mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/puppeteer/node_modules/ws": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.1.tgz", + "integrity": "sha512-2NkHdPKjDBj3CHdnAGNpmlliryKqF+n9MYXX7/wsVC4yqYocKreKNjydPDvT3wShAZnndlM0RytEfTALCDvz7A==", + "dev": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "dev": true + }, + "node_modules/quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/randomatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", + "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", + "dev": true, + "dependencies": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "dependencies": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "dependencies": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body/node_modules/setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + }, + "node_modules/raw-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "dev": true + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "dependencies": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "node_modules/regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "dependencies": { + "is-equal-shallow": "^0.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "dependencies": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "node_modules/regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/renderkid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", + "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "dev": true, + "dependencies": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + } + }, + "node_modules/repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "dependencies": { + "is-finite": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", + "dev": true, + "dependencies": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "optional": true, + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/right-pad": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/right-pad/-/right-pad-1.0.1.tgz", + "integrity": "sha1-jKCMLLtbVedNr6lr9/0aJ9VoyNA=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "dependencies": { + "glob": "^7.0.5" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "dependencies": { + "is-promise": "^2.1.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "dependencies": { + "aproba": "^1.1.1" + } + }, + "node_modules/rxjs": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.1.tgz", + "integrity": "sha512-OwMxHxmnmHTUpgO+V7dZChf3Tixf4ih95cmXjzzadULziVl/FKhHScGLj4goEw9weePVOH2Q0+GcCBUhKCZc/g==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sass": { + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "dev": true, + "dependencies": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0", + "semver": "^5.5.0" + }, + "engines": { + "node": ">= 6.9.0 || >= 8.9.0" + }, + "peerDependencies": { + "webpack": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/sass/node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/sass/node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sass/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/sass/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sass/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/sass/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sass/node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sass/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/sass/node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/sass/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^2.2.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "dev": true + }, + "node_modules/schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "node_modules/selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "dependencies": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "engines": { + "node": ">= 6.9.0" + } + }, + "node_modules/selenium-webdriver/node_modules/tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/selfsigned": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", + "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "dev": true, + "dependencies": { + "node-forge": "0.9.0" + } + }, + "node_modules/semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "devOptional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-dsl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", + "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", + "dev": true, + "dependencies": { + "semver": "^5.3.0" + } + }, + "node_modules/semver-intersect": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.3.1.tgz", + "integrity": "sha1-j6hKnhAovSOeRTDRo+GB5pjYhLo=", + "dev": true, + "dependencies": { + "semver": "^5.0.0" + } + }, + "node_modules/semver-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz", + "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/send/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "node_modules/send/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "deprecated": "Critical bug fixed in v3.0.1, please upgrade to the latest version.", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shelljs": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", + "integrity": "sha1-N5zM+1a5HIYB5HkzVutTgpJN6a0=", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "iojs": "*", + "node": ">=0.11.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/silent-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/silent-error/-/silent-error-1.1.0.tgz", + "integrity": "sha1-IglwbxyFCp8dENDYQJGLRvJuG8k=", + "dev": true, + "dependencies": { + "debug": "^2.2.0" + } + }, + "node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/socket.io": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.3.tgz", + "integrity": "sha1-uK+cq6AJSeVo42nxMn6pvp6iRhs=", + "dev": true, + "dependencies": { + "debug": "2.3.3", + "engine.io": "1.8.3", + "has-binary": "0.1.7", + "object-assign": "4.1.0", + "socket.io-adapter": "0.5.0", + "socket.io-client": "1.7.3", + "socket.io-parser": "2.3.1" + } + }, + "node_modules/socket.io-adapter": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", + "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=", + "dev": true, + "dependencies": { + "debug": "2.3.3", + "socket.io-parser": "2.3.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "dependencies": { + "ms": "0.7.2" + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "node_modules/socket.io-client": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.3.tgz", + "integrity": "sha1-sw6GqhDV7zVGYBwJzeR2Xjgdo3c=", + "dev": true, + "dependencies": { + "backo2": "1.0.2", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "2.3.3", + "engine.io-client": "1.8.3", + "has-binary": "0.1.7", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseuri": "0.0.5", + "socket.io-parser": "2.3.1", + "to-array": "0.1.4" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "dependencies": { + "ms": "0.7.2" + } + }, + "node_modules/socket.io-client/node_modules/ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "node_modules/socket.io-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", + "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=", + "dev": true, + "dependencies": { + "component-emitter": "1.1.2", + "debug": "2.2.0", + "isarray": "0.0.1", + "json3": "3.3.2" + } + }, + "node_modules/socket.io-parser/node_modules/component-emitter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=", + "dev": true + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "dependencies": { + "ms": "0.7.1" + } + }, + "node_modules/socket.io-parser/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "node_modules/socket.io/node_modules/debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "dependencies": { + "ms": "0.7.2" + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "node_modules/socket.io/node_modules/object-assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "node_modules/sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "dev": true, + "dependencies": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + } + }, + "node_modules/sockjs-client/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/sockjs-client/node_modules/faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/sockjs-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "dependencies": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/source-map-loader/node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", + "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", + "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/spdy-transport/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/spdy/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/spdy/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "dev": true, + "dependencies": { + "through2": "^2.0.2" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "dashdash": "^1.12.0", + "getpass": "^0.1.1", + "safer-buffer": "^2.0.2" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + }, + "optionalDependencies": { + "bcrypt-pbkdf": "^1.0.0", + "ecc-jsbn": "~0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" + } + }, + "node_modules/ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/standard-version": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-4.4.0.tgz", + "integrity": "sha512-jJ8FZhnmh9xJRQLnaXiGRLaAUNItIH29lOQZGpL5fd4+jUHto9Ij6SPCYN86h6ZNNXkYq2TYiIVVF7gVyC+pcQ==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "conventional-changelog": "^1.1.0", + "conventional-recommended-bump": "^1.0.0", + "dotgitignore": "^1.0.3", + "figures": "^1.5.0", + "fs-access": "^1.0.0", + "semver": "^5.1.0", + "yargs": "^8.0.1" + }, + "bin": { + "standard-version": "bin/cli.js" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/standard-version/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/standard-version/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/standard-version/node_modules/cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/standard-version/node_modules/cliui/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/standard-version/node_modules/figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/standard-version/node_modules/load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "dependencies": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "dependencies": { + "pify": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/standard-version/node_modules/read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "dependencies": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/standard-version/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/standard-version/node_modules/y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "node_modules/standard-version/node_modules/yargs": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", + "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "node_modules/standard-version/node_modules/yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stats-webpack-plugin": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/stats-webpack-plugin/-/stats-webpack-plugin-0.6.2.tgz", + "integrity": "sha1-LFlJtTHgf4eojm6k3PrFOqjHWis=", + "dev": true, + "dependencies": { + "lodash": "^4.17.4" + }, + "peerDependencies": { + "webpack": ">=1.0.0" + } + }, + "node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "optional": true, + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "dependencies": { + "get-stdin": "^4.0.1" + }, + "bin": { + "strip-indent": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-loader": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz", + "integrity": "sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "schema-utils": "^0.4.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "dev": true, + "dependencies": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + }, + "bin": { + "stylus": "bin/stylus" + }, + "engines": { + "node": "*" + } + }, + "node_modules/stylus-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", + "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", + "dev": true, + "dependencies": { + "loader-utils": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "when": "~3.6.x" + }, + "peerDependencies": { + "stylus": ">=0.52.4" + } + }, + "node_modules/stylus/node_modules/glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/stylus/node_modules/source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/text-mask-addons": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/text-mask-addons/-/text-mask-addons-3.7.2.tgz", + "integrity": "sha512-uezfAuWcDJ/B/lu5ooz38DEdI8NfIVX2xKwshLs40Er8MBGSppklOqOYw32wEmCUP7XPVX2u3u2TdQcIRuBLXA==" + }, + "node_modules/text-mask-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/text-mask-core/-/text-mask-core-5.1.1.tgz", + "integrity": "sha512-tcHJSs0jlHpIed2flCuZZhUIGN1KfDsN/qCW7lkYxyXkKt4OLd3GXCHPFsY9rOLhmyC43ZRTM+tCxizBzluWpw==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "dependencies": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-off-newlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "optional": true, + "dependencies": { + "glob": "^7.1.2" + } + }, + "node_modules/ts-node": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-5.0.1.tgz", + "integrity": "sha512-XK7QmDcNHVmZkVtkiwNDWiERRHPyU8nBqZB1+iv2UhOG0q3RQ9HsZ2CMqISlFbxjrYFGfG2mX7bW4dAyxBVzUw==", + "dev": true, + "dependencies": { + "arrify": "^1.0.0", + "chalk": "^2.3.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.3", + "yn": "^2.0.0" + }, + "bin": { + "ts-node": "dist/bin.js" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ts-node/node_modules/chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ts-node/node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/tsickle": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.29.0.tgz", + "integrity": "sha512-JpID0Lv8/irRtPmqJJxb5fCwfZhjZeKmav9Zna7UjqVuJoSbI49Wue/c2PPybX1SbRrjl7bbI/JsCl0dSUJygA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map": "^0.6.0", + "source-map-support": "^0.5.0" + }, + "bin": { + "tsickle": "src/main.js" + }, + "peerDependencies": { + "typescript": ">=2.4.2 <2.9" + } + }, + "node_modules/tsickle/node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/tsickle/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz", + "integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw==" + }, + "node_modules/tslint": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", + "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.12.1" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev" + } + }, + "node_modules/tslint/node_modules/chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tsutils": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", + "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev" + } + }, + "node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/typescript": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", + "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "dependencies": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uglify-js/node_modules/commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "node_modules/uglifyjs-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", + "dev": true, + "dependencies": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "engines": { + "node": ">= 4.8 < 5.0.0 || >= 5.10" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "deprecated": "support for ECMAScript is superseded by `uglify-js` as of v3.13.0", + "dev": true, + "dependencies": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", + "dev": true + }, + "node_modules/union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "deprecated": "Critical bug fixed in v3.0.1, please upgrade to the latest version.", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-loader": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", + "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^1.0.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "webpack": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/url-loader/node_modules/mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/use": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", + "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "dev": true, + "dependencies": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + } + }, + "node_modules/util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/validate-commit-msg": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/validate-commit-msg/-/validate-commit-msg-2.14.0.tgz", + "integrity": "sha1-5Tg2kQEsuycNzAvCpO/+vhSJDqw=", + "deprecated": "Check out CommitLint which provides the same functionality with a more user-focused experience.", + "dev": true, + "dependencies": { + "conventional-commit-types": "^2.0.0", + "find-parent-dir": "^0.3.0", + "findup": "0.1.5", + "semver-regex": "1.0.0" + }, + "bin": { + "validate-commit-msg": "lib/cli.js" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "dependencies": { + "builtins": "^1.0.3" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "dependencies": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webdriver-js-extender": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-1.0.0.tgz", + "integrity": "sha1-gcUzqeM9W/tZe05j4s2yW1R3dRU=", + "dev": true, + "dependencies": { + "@types/selenium-webdriver": "^2.53.35", + "selenium-webdriver": "^2.53.2" + } + }, + "node_modules/webdriver-js-extender/node_modules/sax": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.6.1.tgz", + "integrity": "sha1-VjsZx8HeiS4Jv8Ty/DDjwn8JUrk=", + "dev": true + }, + "node_modules/webdriver-js-extender/node_modules/selenium-webdriver": { + "version": "2.53.3", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-2.53.3.tgz", + "integrity": "sha1-0p/1qVff8aG0ncRXdW5OS/vc4IU=", + "dev": true, + "dependencies": { + "adm-zip": "0.4.4", + "rimraf": "^2.2.8", + "tmp": "0.0.24", + "ws": "^1.0.1", + "xml2js": "0.4.4" + }, + "engines": { + "node": ">= 4.2.x" + } + }, + "node_modules/webdriver-js-extender/node_modules/tmp": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.24.tgz", + "integrity": "sha1-1qXhmNFKmDXMby18PZ4wJCjIzxI=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webdriver-js-extender/node_modules/xml2js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.4.tgz", + "integrity": "sha1-MREBAAMAiuGSQOuhdJe1fHKcVV0=", + "dev": true, + "dependencies": { + "sax": "0.6.x", + "xmlbuilder": ">=1.0.0" + } + }, + "node_modules/webpack": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.4.tgz", + "integrity": "sha512-RqUfwp4qMqv3oFwBQQOoK69C2tdu2FHJEqPABPqgjGDvOIOLqkTOhmmdJjpiRabzNAAH1ahmkA3z4xowlHN+VA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/wasm-edit": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "acorn": "^5.6.2", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.0.0", + "uglifyjs-webpack-plugin": "^1.2.4", + "watchpack": "^1.5.0", + "webpack-sources": "^1.0.1" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "dependencies": { + "source-list-map": "~0.1.7", + "source-map": "~0.4.1" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/webpack-core/node_modules/source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "node_modules/webpack-core/node_modules/source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz", + "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", + "dev": true, + "dependencies": { + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz", + "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==", + "dev": true, + "dependencies": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.18.0", + "import-local": "^2.0.0", + "internal-ip": "^3.0.1", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "schema-utils": "^1.0.0", + "selfsigned": "^1.9.1", + "semver": "^5.6.0", + "serve-index": "^1.7.2", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "3.4.0", + "webpack-log": "^2.0.0", + "yargs": "12.0.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 6.11.5" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "dependencies": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/cliui/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/webpack-dev-server/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/webpack-dev-server/node_modules/decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "dependencies": { + "xregexp": "4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "dependencies": { + "invert-kv": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "dependencies": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "dependencies": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/webpack-dev-server/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/webpack-dev-server/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/yargs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "dev": true, + "dependencies": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + } + }, + "node_modules/webpack-dev-server/node_modules/yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "dependencies": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/webpack-merge": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.4.tgz", + "integrity": "sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.5" + } + }, + "node_modules/webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack-sources/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "1.1.0-rc.4", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.4.tgz", + "integrity": "sha1-xcTj1pD50vZKlVDgeodn+Xlqpdg=", + "dev": true, + "dependencies": { + "webpack-core": "^0.6.8" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "webpack": "^1.12.11 || ~2 || ~3 || ~4" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", + "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.4.0 <0.4.11", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/when": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", + "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", + "dev": true + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "node_modules/worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "dependencies": { + "errno": "~0.1.7" + } + }, + "node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/ws": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz", + "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=", + "dev": true, + "dependencies": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + }, + "node_modules/wtf-8": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", + "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=", + "dev": true + }, + "node_modules/xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "node_modules/xml2js/node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "node_modules/xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", + "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "dependencies": { + "cuint": "^0.2.2" + } + }, + "node_modules/y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true, + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "optional": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs-parser/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "dependencies": { + "fd-slicer": "~1.0.1" + } + }, + "node_modules/yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "node_modules/yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/zone.js": { + "version": "0.8.26", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.26.tgz", + "integrity": "sha512-W9Nj+UmBJG251wkCacIkETgra4QgBo/vgoEkb4a2uoLzpQG7qF9nzwoLXWU5xj3Fg2mxGvEDh47mg24vXccYjA==" + } + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.6.8.tgz", + "integrity": "sha512-ZKTm/zC61iY9IBHOEAKoMSzZpvhkmv+1O/HHzpHEuR551jCzu6vSyCmMY9Z7GBcccscCV+hjeSMwgFrFRcqlkw==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.6.8", + "rxjs": "^6.0.0" + } + }, + "@angular-devkit/build-angular": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.8.9.tgz", + "integrity": "sha512-J6o0MwIG1cJT29p87c7uUn7NY3QLEoQOVw4VXWM9cqG9bv99VK7f7eOSDhHJbXn7Snm4XYrye0zRa3RFXhMG+A==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.8.9", + "@angular-devkit/build-optimizer": "0.8.9", + "@angular-devkit/build-webpack": "0.8.9", + "@angular-devkit/core": "0.8.9", + "@ngtools/webpack": "6.2.9", + "ajv": "6.4.0", + "autoprefixer": "8.6.5", + "circular-dependency-plugin": "5.0.2", + "clean-css": "4.2.1", + "copy-webpack-plugin": "4.5.2", + "file-loader": "1.1.11", + "glob": "7.1.3", + "html-webpack-plugin": "3.2.0", + "istanbul": "0.4.5", + "istanbul-instrumenter-loader": "3.0.1", + "karma-source-map-support": "1.3.0", + "less": "3.8.1", + "less-loader": "4.1.0", + "license-webpack-plugin": "1.5.0", + "loader-utils": "1.1.0", + "mini-css-extract-plugin": "0.4.4", + "minimatch": "3.0.4", + "node-sass": "^4.9.3", + "opn": "5.4.0", + "parse5": "4.0.0", + "portfinder": "1.0.19", + "postcss": "6.0.23", + "postcss-import": "11.1.0", + "postcss-loader": "2.1.6", + "postcss-url": "7.3.2", + "raw-loader": "0.5.1", + "rxjs": "6.2.2", + "sass-loader": "7.1.0", + "semver": "5.6.0", + "source-map-loader": "0.2.4", + "source-map-support": "0.5.9", + "stats-webpack-plugin": "0.6.2", + "style-loader": "0.21.0", + "stylus": "0.54.5", + "stylus-loader": "3.0.2", + "tree-kill": "1.2.1", + "uglifyjs-webpack-plugin": "1.3.0", + "url-loader": "1.1.2", + "webpack": "4.16.4", + "webpack-dev-middleware": "3.4.0", + "webpack-dev-server": "3.1.14", + "webpack-merge": "4.1.4", + "webpack-sources": "1.3.0", + "webpack-subresource-integrity": "1.1.0-rc.4" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.8.9.tgz", + "integrity": "sha512-2tiGPkvJyFY/G3a27uC8r6Jj3H5m8SxjMqhjNUQ5AtNumweTBPt3YIYMNAvHUmxG0nA9upDolVXFmoQGK9AhKQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.8.9", + "rxjs": "6.2.2" + } + }, + "@angular-devkit/core": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.9.tgz", + "integrity": "sha512-Umax3YKBPTQy360TeoSNaIIOJOKoXvN/S2WNTV8wDjSWWNiWLTIlckWMb9DVsafAifjUi0mtOLRFuM4YatKgTw==", + "dev": true, + "requires": { + "ajv": "6.4.0", + "chokidar": "2.0.4", + "rxjs": "6.2.2", + "source-map": "0.5.7" + } + }, + "ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha512-uWK8ISUH3jdo9gTIgylnj4QQDKFL6SbQcx9LtwXJ+2biBCSNIhc41aYJO0W1/w+6tEMPsopQ0cOCnWX39u5o0w==", + "dev": true, + "requires": { + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" + } + }, + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "optional": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "optional": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "dev": true, + "optional": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "optional": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "optional": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "optional": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "dev": true, + "optional": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true, + "optional": true + } + } + }, + "node-sass": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", + "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", + "dev": true, + "optional": true, + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash": "^4.17.15", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.13.2", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "2.2.5", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + } + }, + "opn": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", + "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "optional": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "optional": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "optional": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "optional": true + }, + "rxjs": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", + "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "sass-graph": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", + "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^13.3.2" + } + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "dev": true, + "optional": true, + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "optional": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "dev": true, + "optional": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + }, + "uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha512-SoboS4c924cg+wR2vxl8fospPPli3ZmVPIkRpJEWcrGIPeE8Tr3m9zNIyjYKn9YlF8EgiXQDCy3XVZxSFNjh8A==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "optional": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + } + } + }, + "@angular-devkit/build-optimizer": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.8.9.tgz", + "integrity": "sha512-h8u5iAhSmt0TsLDZXZCmOkXZDMgP2itLkgZvOIsGInyMAESJuWK4P1qegMSv2R5ELOsinJiuhe218M4K2enEdA==", + "dev": true, + "requires": { + "loader-utils": "1.1.0", + "source-map": "0.5.7", + "typescript": "2.9.2", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "dev": true + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.8.9.tgz", + "integrity": "sha512-2csJ6utodPSLABTXfBLymYLrndJURF3xVqVjEDzUFl9zLqK1YOkKH4XPr12vfH8SfAtvzIutNLRxBtAuWJmDlw==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.8.9", + "@angular-devkit/core": "0.8.9", + "rxjs": "6.2.2" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.8.9.tgz", + "integrity": "sha512-2tiGPkvJyFY/G3a27uC8r6Jj3H5m8SxjMqhjNUQ5AtNumweTBPt3YIYMNAvHUmxG0nA9upDolVXFmoQGK9AhKQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.8.9", + "rxjs": "6.2.2" + } + }, + "@angular-devkit/core": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.9.tgz", + "integrity": "sha512-Umax3YKBPTQy360TeoSNaIIOJOKoXvN/S2WNTV8wDjSWWNiWLTIlckWMb9DVsafAifjUi0mtOLRFuM4YatKgTw==", + "dev": true, + "requires": { + "ajv": "6.4.0", + "chokidar": "2.0.4", + "rxjs": "6.2.2", + "source-map": "0.5.7" + } + }, + "ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha512-uWK8ISUH3jdo9gTIgylnj4QQDKFL6SbQcx9LtwXJ+2biBCSNIhc41aYJO0W1/w+6tEMPsopQ0cOCnWX39u5o0w==", + "dev": true, + "requires": { + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "rxjs": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", + "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha512-SoboS4c924cg+wR2vxl8fospPPli3ZmVPIkRpJEWcrGIPeE8Tr3m9zNIyjYKn9YlF8EgiXQDCy3XVZxSFNjh8A==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + } + } + }, + "@angular-devkit/core": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.6.8.tgz", + "integrity": "sha512-rkIa1OSVWTt4g9leLSK/PsqOj3HZbDKHbZjqlslyfVa3AyCeiumFoOgViOVXlYgPX3HHDbE5uH24nyUWSD8uww==", + "dev": true, + "requires": { + "ajv": "~6.4.0", + "chokidar": "^2.0.3", + "rxjs": "^6.0.0", + "source-map": "^0.5.6" + }, + "dependencies": { + "ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha512-uWK8ISUH3jdo9gTIgylnj4QQDKFL6SbQcx9LtwXJ+2biBCSNIhc41aYJO0W1/w+6tEMPsopQ0cOCnWX39u5o0w==", + "dev": true, + "requires": { + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" + } + }, + "uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha512-SoboS4c924cg+wR2vxl8fospPPli3ZmVPIkRpJEWcrGIPeE8Tr3m9zNIyjYKn9YlF8EgiXQDCy3XVZxSFNjh8A==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + } + } + }, + "@angular-devkit/schematics": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.6.8.tgz", + "integrity": "sha512-R4YqAUdo62wtrhX/5HSRGSKXNTWqfQb66ZE6m8jj6GEJNFKdNXMdxOchxr07LCiKTxfh1w6G3nGzxIsu/+D4KA==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.6.8", + "rxjs": "^6.0.0" + } + }, + "@angular/animations": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-6.0.4.tgz", + "integrity": "sha512-Ro1XEwShk8XRAogfu73fKLTBLnND1s+MGhN+ymwr7ib9hqxVr7jMNE+MXPqG2/1BX9c+7NHb30B8G4woQn62vw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/cdk": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.4.7.tgz", + "integrity": "sha512-18x0U66fLD5kGQWZ9n3nb75xQouXlWs7kUDaTd8HTrHpT1s2QIAqlLd1KxfrYiVhsEC2jPQaoiae7VnBlcvkBg==", + "requires": { + "tslib": "^1.7.1" + } + }, + "@angular/cli": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-6.0.8.tgz", + "integrity": "sha512-DhH1Zq5Yonthw6zh6W07fhf+9XrAZbD1fcQ0MrmbxlieCfLlTAdBqyK2LavFCKwSZkUMLF6UHM3+jiNRVZSSIg==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.6.8", + "@angular-devkit/core": "0.6.8", + "@angular-devkit/schematics": "0.6.8", + "@schematics/angular": "0.6.8", + "@schematics/update": "0.6.8", + "opn": "~5.3.0", + "resolve": "^1.1.7", + "rxjs": "^6.0.0", + "semver": "^5.1.0", + "silent-error": "^1.0.0", + "symbol-observable": "^1.2.0", + "yargs-parser": "^10.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "yargs-parser": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.0.0.tgz", + "integrity": "sha512-+DHejWujTVYeMHLff8U96rLc4uE4Emncoftvn5AjhB1Jw1pWxLzgBUT/WYbPrHmy6YPEBTZQx5myHhVcuuu64g==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "@angular/common": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-6.0.4.tgz", + "integrity": "sha512-z2UE6a43u49omsqjzwdHhwD+wpCPauuD13FMkVQMLwzugKhNri0LjMk5I0I9xLz9OyXICm2B+wPAQN4d07savg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-6.0.4.tgz", + "integrity": "sha512-qwjuPacuRKUroD+xev653mljV9Jwhi2tvwGo/cVadq0isJ2rVH1hO6sa1FWFnNf1KPnyolMobZlMgg8DFW/yRw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/compiler-cli": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-6.0.4.tgz", + "integrity": "sha512-bkN21JJrsbIdXNfOc9I9iB7AETUe4/QvApC25R3/pERSyhkK7UNNHTqg4FY5xMdSxlZd1ccWj8rvSbS+hFbTBw==", + "dev": true, + "requires": { + "chokidar": "^1.4.2", + "minimist": "^1.2.0", + "reflect-metadata": "^0.1.2", + "tsickle": "^0.29.0" + }, + "dependencies": { + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "@angular/core": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-6.0.4.tgz", + "integrity": "sha512-WCW8wyRoIKkjGjOjVLFpUyhQw/K0//s3W0qo1/vH6m9njC63jP/VP+2Xnt8bdtgeET2NYUeyl+w/0vUXqYlnEA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/forms": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-6.0.4.tgz", + "integrity": "sha512-JcKyCCkAwF3NlWuwPxtpRhFX/8QbFUkA4W0N62L+Odm8G0/P4QhMw/99o5kRs5xrsQRVyFlGvdYOrkgDyyZkGg==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/http": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-6.0.4.tgz", + "integrity": "sha512-bq7cS0/5hM3Af3FolEe7HpKP19/7MJWIPGi5k9rYCGM4dXKFiKa+LA/SbXE0DMiKxhAQbZDwQ9HN04r70DRyBQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/language-service": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-6.0.4.tgz", + "integrity": "sha512-LyFh/sFtnyjJJYO5UKM31nGSSkmGYFMwqzwbG+l0QNQXNoa9jj+OApvu0oOY5F0WP9DBrOAzj74j2M5olprtMQ==", + "dev": true + }, + "@angular/material": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-6.4.7.tgz", + "integrity": "sha512-SdNx7Xovi24Kw9eU6lkLhY/7f2M7L9F+/uh6XuPr4jbGgCUVVpeeVI5ztZhsZRbj1sN+/r1p5w8u62apWWl5Ww==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^1.7.1" + }, + "dependencies": { + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "optional": true + } + } + }, + "@angular/platform-browser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-6.0.4.tgz", + "integrity": "sha512-4y6rSqOLuBs+RL0CIM+RZ5T8a5Tm1NisZcTUGvM8lVYPNkqTzNfHQXhBq8mTgRw9JopEwOj+UEqBbgrNX715yQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.0.4.tgz", + "integrity": "sha512-uh7MoQi9kyKPFHrD7HPOU5gWyhF4pPJuGltyMTOtM/f5TNL9QQI2EugijKnnSFLQ8+7UQjmFAVeRqH9jylhIyQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@angular/router": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-6.0.4.tgz", + "integrity": "sha512-IKUtbbRtHN8I+Hp8/pLqBPyXJmQHrPFGzKU3H43TovD++mmT5AaYVCeJgkGUB//wjtLN3y0X/yrmyWFWcN35Gw==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@fortawesome/fontawesome": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome/-/fontawesome-1.1.8.tgz", + "integrity": "sha512-c0/MtkPVT0fmiFcCyYoPjkG9PkMOvfrZw2+0BaJ+Rh6UEcK1AR/LaRgrHHjUkbAbs9LXxQJhFS8CJ4uSnK2+JA==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.1.7" + } + }, + "@fortawesome/fontawesome-common-types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.1.7.tgz", + "integrity": "sha512-ego8jRVSHfq/iq4KRZJKQeUAdi3ZjGNrqw4oPN3fNdvTBnLCSntwVCnc37bsAJP9UB8MhrTfPnZYxkv2vpS4pg==" + }, + "@fortawesome/fontawesome-free-regular": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-regular/-/fontawesome-free-regular-5.0.13.tgz", + "integrity": "sha512-36lz9Idww1L4QaaTcv7GZiOeIP9emJFDUsedvRovI10kmwyd6rN0PKkIjnq0FB4foLhX4Rou8vnbCCmjtqiLug==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.1.7" + } + }, + "@fortawesome/fontawesome-free-solid": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free-solid/-/fontawesome-free-solid-5.0.13.tgz", + "integrity": "sha512-b+krVnqkdDt52Yfev0x0ZZgtxBQsLw00Zfa3uaVWIDzpNZVtrEXuxldUSUaN/ihgGhSNi8VpvDAdNPVgCKOSxw==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.1.7" + } + }, + "@ng-bootstrap/ng-bootstrap": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-2.1.0.tgz", + "integrity": "sha512-ElqT/uayvnAbA9Klt0IZSbQ+BZtyEPdGDGojmYWQEFDNmTmismkM9Cohx4Hy6PakGAItCy7Ghu1isR8ZKkdtnw==", + "requires": {} + }, + "@ngtools/webpack": { + "version": "6.2.9", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-6.2.9.tgz", + "integrity": "sha512-wZ9ROI4FdA9gnx21ULtA8u6Gmtig3BbT6EfUh2uYisjyL4P9k2p22sfY/Txwu/InnF4LDHOs5xZutOnGvC87vw==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.8.9", + "rxjs": "6.2.2", + "tree-kill": "1.2.1", + "webpack-sources": "1.3.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.9.tgz", + "integrity": "sha512-Umax3YKBPTQy360TeoSNaIIOJOKoXvN/S2WNTV8wDjSWWNiWLTIlckWMb9DVsafAifjUi0mtOLRFuM4YatKgTw==", + "dev": true, + "requires": { + "ajv": "6.4.0", + "chokidar": "2.0.4", + "rxjs": "6.2.2", + "source-map": "0.5.7" + } + }, + "ajv": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", + "integrity": "sha512-uWK8ISUH3jdo9gTIgylnj4QQDKFL6SbQcx9LtwXJ+2biBCSNIhc41aYJO0W1/w+6tEMPsopQ0cOCnWX39u5o0w==", + "dev": true, + "requires": { + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0", + "uri-js": "^3.0.2" + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "rxjs": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", + "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "uri-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", + "integrity": "sha512-SoboS4c924cg+wR2vxl8fospPPli3ZmVPIkRpJEWcrGIPeE8Tr3m9zNIyjYKn9YlF8EgiXQDCy3XVZxSFNjh8A==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + } + } + }, + "@ngx-translate/core": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-10.0.2.tgz", + "integrity": "sha512-7nM3DrJaqKswwtJlbu2kuKNl+hE8Isr18sKsKvGGpSxQk+G0gO0reDlx2PhUNus7TJTkA1C59vU/JoN8hIvZ4g==", + "requires": { + "tslib": "^1.9.0" + } + }, + "@ngx-translate/http-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-3.0.1.tgz", + "integrity": "sha1-ILD5i8bCUyESnT4zAqs8xInApCo=", + "requires": { + "tslib": "^1.9.0" + } + }, + "@schematics/angular": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.6.8.tgz", + "integrity": "sha512-9kRphqTYG5Df/I8fvnT1zMsw0YNDPO9tl18tQZXj4am4raT7l9UCr+WkwJdlBoA5pwG6baWE9sL0iGWV/bzF/g==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.6.8", + "@angular-devkit/schematics": "0.6.8", + "typescript": ">=2.6.2 <2.8" + } + }, + "@schematics/update": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.6.8.tgz", + "integrity": "sha512-1Uq7LYnwL2wBwGVCgNz76QAR13ghAk+2vDDHOi+VX5+usHManxydrpoMGeX66OBPd+y5D3D2MFb+8mYHE7mygg==", + "dev": true, + "requires": { + "@angular-devkit/core": "0.6.8", + "@angular-devkit/schematics": "0.6.8", + "npm-registry-client": "^8.5.1", + "rxjs": "^6.0.0", + "semver": "^5.3.0", + "semver-intersect": "^1.1.2" + } + }, + "@types/adal": { + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@types/adal/-/adal-1.0.29.tgz", + "integrity": "sha512-6kvyTWzue5lJ8xECAgVdMrs5+UEyPeflzU/L83QdufGnvjdyCiCWVgkfinMB7133ik/x+02T0q1070Z6/ZK6eQ==", + "requires": { + "@types/angular": "*" + } + }, + "@types/angular": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.51.tgz", + "integrity": "sha512-wYU+/zlJWih7ZmonWVjGQ18tG7GboI9asMNjRBM5fpIFJWXSioQttCTw9qGL44cP82ghM8sCV9apEqm1zBDq2w==" + }, + "@types/jasmine": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.8.tgz", + "integrity": "sha512-OJSUxLaxXsjjhob2DBzqzgrkLmukM3+JMpRp0r0E4HTdT1nwDCWhaswjYxazPij6uOdzHCJfNbDjmQ1/rnNbCg==", + "dev": true + }, + "@types/jasminewd2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.3.tgz", + "integrity": "sha512-hYDVmQZT5VA2kigd4H4bv7vl/OhlympwREUemqBdOqtrYTo5Ytm12a5W5/nGgGYdanGVxj0x/VhZ7J3hOg/YKg==", + "dev": true, + "requires": { + "@types/jasmine": "*" + } + }, + "@types/node": { + "version": "8.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz", + "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==", + "dev": true + }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "@types/selenium-webdriver": { + "version": "2.53.43", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-2.53.43.tgz", + "integrity": "sha512-UBYHWph6P3tutkbXpW6XYg9ZPbTKjw/YC2hGG1/GEvWwTbvezBUv3h+mmUFw79T3RFPnmedpiXdOBbXX+4l0jg==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz", + "integrity": "sha512-49nwvW/Hx9i+OYHg+mRhKZfAlqThr11Dqz8TsrvqGKMhdI2ijy3KBJOun2Z4770TPjrIJhR6KxChQIDaz8clDA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "debug": "^3.1.0", + "mamacro": "^0.0.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz", + "integrity": "sha512-vrvvB18Kh4uyghSKb0NTv+2WZx871WL2NzwMj61jcq2bXkyhRC+8Q0oD7JGVf0+5i/fKQYQSBCNMMsDMRVAMqA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz", + "integrity": "sha512-dBh2CWYqjaDlvMmRP/kudxpdh30uXjIbpkLj9HQe+qtYlwvYjPRjdQXrq1cTAAOUSMTtzqbXIxEdEZmyKfcwsg==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz", + "integrity": "sha512-v7igWf1mHcpJNbn4m7e77XOAWXCDT76Xe7Is1VQFXc4K5jRcFrl9D0NrqM4XifQ0bXiuTSkTKMYqDxu5MhNljA==", + "dev": true, + "requires": { + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz", + "integrity": "sha512-yN6ScQQDFCiAXnVctdVO/J5NQRbwyTbQzsGzEgXsAnrxhjp0xihh+nNHQTMrq5UhOqTb5LykpJAvEv9AT0jnAQ==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.5.13" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz", + "integrity": "sha512-hSIKzbXjVMRvy3Jzhgu+vDd/aswJ+UMEnLRCkZDdknZO3Z9e6rp1DAs0tdLItjCFqkz9+0BeOPK/mk3eYvVzZg==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz", + "integrity": "sha512-zxJXULGPLB7r+k+wIlvGlXpT4CYppRz8fLUM/xobGHc9Z3T6qlmJD9ySJ2jknuktuuiR9AjnNpKYDECyaiX+QQ==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "mamacro": "^0.0.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz", + "integrity": "sha512-0n3SoNGLvbJIZPhtMFq0XmmnA/YmQBXaZKQZcW8maGKwLpVcgjNrxpFZHEOLKjXJYVN5Il8vSfG7nRX50Zn+aw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz", + "integrity": "sha512-IJ/goicOZ5TT1axZFSnlAtz4m8KEjYr12BNOANAwGFPKXM4byEDaMNXYowHMG0yKV9a397eU/NlibFaLwr1fbw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@webassemblyjs/ieee754": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz", + "integrity": "sha512-TseswvXEPpG5TCBKoLx9tT7+/GMACjC1ruo09j46ULRZWYm8XHpDWaosOjTnI7kr4SRJFzA6MWoUkAB+YCGKKg==", + "dev": true, + "requires": { + "ieee754": "^1.1.11" + } + }, + "@webassemblyjs/leb128": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.5.13.tgz", + "integrity": "sha512-0NRMxrL+GG3eISGZBmLBLAVjphbN8Si15s7jzThaw1UE9e5BY1oH49/+MA1xBzxpf1OW5sf9OrPDOclk9wj2yg==", + "dev": true, + "requires": { + "long": "4.0.0" + }, + "dependencies": { + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + } + } + }, + "@webassemblyjs/utf8": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.5.13.tgz", + "integrity": "sha512-Ve1ilU2N48Ew0lVGB8FqY7V7hXjaC4+PeZM+vDYxEd+R2iQ0q+Wb3Rw8v0Ri0+rxhoz6gVGsnQNb4FjRiEH/Ng==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz", + "integrity": "sha512-X7ZNW4+Hga4f2NmqENnHke2V/mGYK/xnybJSIXImt1ulxbCOEs/A+ZK/Km2jgihjyVxp/0z0hwIcxC6PrkWtgw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/helper-wasm-section": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "@webassemblyjs/wast-printer": "1.5.13", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz", + "integrity": "sha512-yfv94Se8R73zmr8GAYzezFHc3lDwE/lBXQddSiIZEKZFuqy7yWtm3KMwA1uGbv5G1WphimJxboXHR80IgX1hQA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz", + "integrity": "sha512-IkXSkgzVhQ0QYAdIayuCWMmXSYx0dHGU8Ah/AxJf1gBvstMWVnzJnBwLsXLyD87VSBIcsqkmZ28dVb0mOC3oBg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz", + "integrity": "sha512-XnYoIcu2iqq8/LrtmdnN3T+bRjqYFjRHqWbqK3osD/0r/Fcv4d9ecRzjVtC29ENEuNTK4mQ9yyxCBCbK8S/cpg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz", + "integrity": "sha512-Lbz65T0LQ1LgzKiUytl34CwuhMNhaCLgrh0JW4rJBN6INnBB8NMwUfQM+FxTnLY9qJ+lHJL/gCM5xYhB9oWi4A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/floating-point-hex-parser": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-code-frame": "1.5.13", + "@webassemblyjs/helper-fsm": "1.5.13", + "long": "^3.2.0", + "mamacro": "^0.0.3" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz", + "integrity": "sha512-QcwogrdqcBh8Z+eUF8SG+ag5iwQSXxQJELBEHmLkk790wgQgnIMmntT2sMAMw53GiFNckArf5X0bsCA44j3lWQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "long": "^3.2.0" + } + }, + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "dependencies": { + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + } + } + }, + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "dev": true, + "requires": { + "acorn": "^5.0.0" + } + }, + "adal-angular": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/adal-angular/-/adal-angular-1.0.17.tgz", + "integrity": "sha1-bpNuDkH5HTsqiOf/ypwvb29WLMQ=" + }, + "adm-zip": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", + "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=", + "dev": true + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, + "agent-base": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "dependencies": { + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true, + "requires": {} + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true, + "requires": {} + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "angular2-text-mask": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/angular2-text-mask/-/angular2-text-mask-9.0.0.tgz", + "integrity": "sha512-iALcnhJPS1zvX48d86rgUgDe/crX6XfhZrXC4Gdlo2/YwZW7u7KJZY6/b3ieSCIWVq/E6p+wDCzeo3E6leRjDA==", + "requires": { + "text-mask-core": "^5.0.0" + } + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "app-root-path": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", + "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", + "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "dev": true, + "optional": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "dev": true + }, + "autoprefixer": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-8.6.5.tgz", + "integrity": "sha512-PLWJN3Xo/rycNkx+mp8iBDMTm3FeWe4VmYaZDSqL5QQB9sLsQkG5k8n+LNDFnhh9kdq2K+egL/icpctOmDHwig==", + "dev": true, + "requires": { + "browserslist": "^3.2.8", + "caniuse-lite": "^1.0.30000864", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^6.0.23", + "postcss-value-parser": "^3.2.3" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "dev": true, + "requires": { + "callsite": "1.0.0" + } + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, + "blob": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "optional": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + }, + "dependencies": { + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "bootstrap": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.1.tgz", + "integrity": "sha512-SpiDSOcbg4J/PjVSt4ny5eY6j74VbVSjROY4Fb/WIUXBV9cnb5luyR4KnPvNoXuGnBK1T+nJIWqRsvU3yP8Mcg==", + "requires": {} + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cachedir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.1.0.tgz", + "integrity": "sha512-xGBpPqoBvn3unBW7oxgb8aJn42K0m9m1/wyjmazah10Fq7bROGG3kRAE6OIyr3U3PIJUqGuebhCEdMk9OKJG0A==", + "dev": true + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "caniuse-lite": { + "version": "1.0.30001035", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz", + "integrity": "sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.2.2.tgz", + "integrity": "sha512-LvixLAQ4MYhbf7hgL4o5PeK32gJKvVzDRiSNIApDofQvyhl8adgG2lJVXn4+ekQoK7HL9RF8lqxwerpe0x2pCw==", + "dev": true, + "requires": { + "ansi-styles": "^3.1.0", + "escape-string-regexp": "^1.0.5", + "supports-color": "^4.0.0" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } + } + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", + "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.1.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-dependency-plugin": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz", + "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==", + "dev": true, + "requires": {} + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "optional": true + } + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.0", + "shallow-clone": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "codelyzer": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.2.1.tgz", + "integrity": "sha512-CKwfgpfkqi9dyzy4s6ELaxJ54QgJ6A8iTSsM4bzHbLuTpbKncvNc3DUlCvpnkHBhK47gEf4qFsWoYqLrJPhy6g==", + "dev": true, + "requires": { + "app-root-path": "^2.0.1", + "css-selector-tokenizer": "^0.7.0", + "cssauron": "^1.4.0", + "semver-dsl": "^1.0.1", + "source-map": "^0.5.6", + "sprintf-js": "^1.0.3" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "dev": true, + "requires": { + "color-name": "1.1.1" + } + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combine-lists": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", + "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", + "dev": true, + "requires": { + "lodash": "^4.5.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "commitizen": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-3.0.4.tgz", + "integrity": "sha512-djR5F7RBsGALyUEm/B1H/85nsN4L1F5DhWN+9/efSwqHDSyhw2MK6MF2VRuD26PUqGkQbcUlYO61btkTWjcjVw==", + "dev": true, + "requires": { + "cachedir": "2.1.0", + "cz-conventional-changelog": "2.1.0", + "dedent": "0.7.0", + "detect-indent": "^5.0.0", + "find-node-modules": "1.0.4", + "find-root": "1.1.0", + "fs-extra": "^7.0.0", + "glob": "7.1.3", + "inquirer": "6.2.0", + "lodash": "4.17.11", + "minimist": "1.2.0", + "shelljs": "0.7.6", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", + "dev": true + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-func": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", + "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" + } + }, + "compare-versions": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.3.0.tgz", + "integrity": "sha512-MAAAIOdi2s4Gl6rZ76PNcUa9IOYB+5ICdT41o5uMRf09aEu/F9RK+qhe8RjXNPwcTjGV7KU7h2P/fljThFVqyQ==", + "dev": true + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "dependencies": { + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true, + "optional": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "conventional-changelog": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-1.1.24.tgz", + "integrity": "sha512-2WcSUst4Y3Z4hHvoMTWXMJr/DmgVdLiMOVY1Kak2LfFz+GIz2KDp5naqbFesYbfXPmaZ5p491dO0FWZIJoJw1Q==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^1.6.6", + "conventional-changelog-atom": "^0.2.8", + "conventional-changelog-codemirror": "^0.3.8", + "conventional-changelog-core": "^2.0.11", + "conventional-changelog-ember": "^0.3.12", + "conventional-changelog-eslint": "^1.0.9", + "conventional-changelog-express": "^0.3.6", + "conventional-changelog-jquery": "^0.1.0", + "conventional-changelog-jscs": "^0.1.0", + "conventional-changelog-jshint": "^0.3.8", + "conventional-changelog-preset-loader": "^1.1.8" + } + }, + "conventional-changelog-angular": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz", + "integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + } + } + }, + "conventional-changelog-atom": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-0.2.8.tgz", + "integrity": "sha512-8pPZqhMbrnltNBizjoDCb/Sz85KyUXNDQxuAEYAU5V/eHn0okMBVjqc8aHWYpHrytyZWvMGbayOlDv7i8kEf6g==", + "dev": true, + "requires": { + "q": "^1.5.1" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + } + } + }, + "conventional-changelog-codemirror": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.3.8.tgz", + "integrity": "sha512-3HFZKtBXTaUCHvz7ai6nk2+psRIkldDoNzCsom0egDtVmPsvvHZkzjynhdQyULfacRSsBTaiQ0ol6nBOL4dDiQ==", + "dev": true, + "requires": { + "q": "^1.5.1" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + } + } + }, + "conventional-changelog-core": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-2.0.11.tgz", + "integrity": "sha512-HvTE6RlqeEZ/NFPtQeFLsIDOLrGP3bXYr7lFLMhCVsbduF1MXIe8OODkwMFyo1i9ku9NWBwVnVn0jDmIFXjDRg==", + "dev": true, + "requires": { + "conventional-changelog-writer": "^3.0.9", + "conventional-commits-parser": "^2.1.7", + "dateformat": "^3.0.0", + "get-pkg-repo": "^1.0.0", + "git-raw-commits": "^1.3.6", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^1.3.6", + "lodash": "^4.2.1", + "normalize-package-data": "^2.3.5", + "q": "^1.5.1", + "read-pkg": "^1.1.0", + "read-pkg-up": "^1.0.1", + "through2": "^2.0.0" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + } + } + }, + "conventional-changelog-ember": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-0.3.12.tgz", + "integrity": "sha512-mmJzA7uzbrOqeF89dMMi6z17O07ORTXlTMArnLG9ZTX4oLaKNolUlxFUFlFm9JUoVWajVpaHQWjxH1EOQ+ARoQ==", + "dev": true, + "requires": { + "q": "^1.5.1" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + } + } + }, + "conventional-changelog-eslint": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-1.0.9.tgz", + "integrity": "sha512-h87nfVh2fdk9fJIvz26wCBsbDC/KxqCc5wSlNMZbXcARtbgNbNDIF7Y7ctokFdnxkzVdaHsbINkh548T9eBA7Q==", + "dev": true, + "requires": { + "q": "^1.5.1" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + } + } + }, + "conventional-changelog-express": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-0.3.6.tgz", + "integrity": "sha512-3iWVtBJZ9RnRnZveNDzOD8QRn6g6vUif0qVTWWyi5nUIAbuN1FfPVyKdAlJJfp5Im+dE8Kiy/d2SpaX/0X678Q==", + "dev": true, + "requires": { + "q": "^1.5.1" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + } + } + }, + "conventional-changelog-jquery": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-0.1.0.tgz", + "integrity": "sha1-Agg5cWLjhGmG5xJztsecW1+A9RA=", + "dev": true, + "requires": { + "q": "^1.4.1" + } + }, + "conventional-changelog-jscs": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-jscs/-/conventional-changelog-jscs-0.1.0.tgz", + "integrity": "sha1-BHnrRDzH1yxYvwvPDvHURKkvDlw=", + "dev": true, + "requires": { + "q": "^1.4.1" + } + }, + "conventional-changelog-jshint": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-0.3.8.tgz", + "integrity": "sha512-hn9QU4ZI/5V50wKPJNPGT4gEWgiBFpV6adieILW4MaUFynuDYOvQ71EMSj3EznJyKi/KzuXpc9dGmX8njZMjig==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + }, + "dependencies": { + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + } + } + }, + "conventional-changelog-preset-loader": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-1.1.8.tgz", + "integrity": "sha512-MkksM4G4YdrMlT2MbTsV2F6LXu/hZR0Tc/yenRrDIKRwBl/SP7ER4ZDlglqJsCzLJi4UonBc52Bkm5hzrOVCcw==", + "dev": true + }, + "conventional-changelog-writer": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-3.0.9.tgz", + "integrity": "sha512-n9KbsxlJxRQsUnK6wIBRnARacvNnN4C/nxnxCkH+B/R1JS2Fa+DiP1dU4I59mEDEjgnFaN2+9wr1P1s7GYB5/Q==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "conventional-commits-filter": "^1.1.6", + "dateformat": "^3.0.0", + "handlebars": "^4.0.2", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "semver": "^5.5.0", + "split": "^1.0.0", + "through2": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + } + } + }, + "conventional-commit-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-2.2.0.tgz", + "integrity": "sha1-XblXOdbCEqy+e29lahG5QLqmiUY=", + "dev": true + }, + "conventional-commits-filter": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz", + "integrity": "sha512-KcDgtCRKJCQhyk6VLT7zR+ZOyCnerfemE/CsR3iQpzRRFbLEs0Y6rwk3mpDvtOh04X223z+1xyJ582Stfct/0Q==", + "dev": true, + "requires": { + "is-subset": "^0.1.1", + "modify-values": "^1.0.0" + } + }, + "conventional-commits-parser": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz", + "integrity": "sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ==", + "dev": true, + "requires": { + "is-text-path": "^1.0.0", + "JSONStream": "^1.0.4", + "lodash": "^4.2.1", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0", + "trim-off-newlines": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + } + } + }, + "conventional-recommended-bump": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-1.2.1.tgz", + "integrity": "sha512-oJjG6DkRgtnr/t/VrPdzmf4XZv8c4xKVJrVT4zrSHd92KEL+EYxSbYoKq8lQ7U5yLMw7130wrcQTLRjM/T+d4w==", + "dev": true, + "requires": { + "concat-stream": "^1.4.10", + "conventional-commits-filter": "^1.1.1", + "conventional-commits-parser": "^2.1.1", + "git-raw-commits": "^1.3.0", + "git-semver-tags": "^1.3.0", + "meow": "^3.3.0", + "object-assign": "^4.0.1" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-webpack-plugin": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.2.tgz", + "integrity": "sha512-zmC33E8FFSq3AbflTvqvPvBo621H36Afsxlui91d+QyZxPIuXghfnTsa1CuqiAaCPgJoSUWfTFbKJnadZpKEbQ==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + } + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-parse": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz", + "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=", + "dev": true + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-selector-tokenizer": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", + "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "dev": true, + "requires": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "cssauron": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", + "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", + "dev": true, + "requires": { + "through": "X.X.X" + } + }, + "cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true + }, + "cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true + }, + "cz-conventional-changelog": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-2.1.0.tgz", + "integrity": "sha1-L0vHOQ4yROTfKT5ro1Hkx0Cnx2Q=", + "dev": true, + "requires": { + "conventional-commit-types": "^2.0.0", + "lodash.map": "^4.5.1", + "longest": "^1.0.1", + "right-pad": "^1.0.1", + "word-wrap": "^1.0.3" + } + }, + "dargs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", + "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-gateway": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz", + "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", + "dev": true, + "requires": { + "execa": "^0.10.0", + "ip-regex": "^2.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "optional": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-file": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz", + "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", + "dev": true, + "requires": { + "fs-exists-sync": "^0.1.0" + } + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + } + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotgitignore": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-1.0.3.tgz", + "integrity": "sha512-eu5XjSstm0WXQsARgo6kPjkINYZlOUW+z/KtAAIBjHa5mUpMPrxJytbPIndWz6GubBuuuH5ljtVcXKnVnH5q8w==", + "dev": true, + "requires": { + "find-up": "^2.1.0", + "minimatch": "^3.0.4" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "edge-launcher": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/edge-launcher/-/edge-launcher-1.2.2.tgz", + "integrity": "sha1-60Cq+9Bnpup27/+rBke81VCbN7I=", + "dev": true + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.376.tgz", + "integrity": "sha512-cv/PYVz5szeMz192ngilmezyPNFkUjuynuL2vNdiqIrio440nfTDdc0JJU0TS2KHLSVCs9gBbt4CFqM+HcBnjw==", + "dev": true + }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true, + "optional": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.3.tgz", + "integrity": "sha1-jef5eJXSDTm4X4ju7nd7K9QrE9Q=", + "dev": true, + "requires": { + "accepts": "1.3.3", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "2.3.3", + "engine.io-parser": "1.3.2", + "ws": "1.1.2" + }, + "dependencies": { + "accepts": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "dev": true, + "requires": { + "mime-types": "~2.1.11", + "negotiator": "0.6.1" + } + }, + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-client": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.3.tgz", + "integrity": "sha1-F5jtk0USRkU9TG9jXXogH+lA1as=", + "dev": true, + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "2.3.3", + "engine.io-parser": "1.3.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parsejson": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "1.1.2", + "xmlhttprequest-ssl": "1.5.3", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "engine.io-parser": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz", + "integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "0.0.6", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.4", + "has-binary": "0.1.7", + "wtf-8": "1.0.0" + } + }, + "enhanced-resolve": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "dev": true + }, + "events": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", + "dev": true + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-braces": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", + "dev": true, + "requires": { + "array-slice": "^0.2.3", + "array-unique": "^0.2.1", + "braces": "^0.1.2" + }, + "dependencies": { + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", + "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", + "dev": true, + "requires": { + "expand-range": "^0.1.0" + } + }, + "expand-range": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", + "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", + "dev": true, + "requires": { + "is-number": "^0.1.1", + "repeat-string": "^0.2.2" + } + }, + "is-number": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", + "dev": true + }, + "repeat-string": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", + "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", + "dev": true + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "^2.1.0" + }, + "dependencies": { + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", + "dev": true, + "requires": { + "os-homedir": "^1.0.1" + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fastparse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-loader": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" + } + }, + "file-saver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.0.tgz", + "integrity": "sha512-cYM1ic5DAkg25pHKgi5f10ziAM7RJU37gaH1XQlyNDrtUnzhC/dfoV9zf2OmF0RMKi42jG5B0JWBnPQqyj/G6g==" + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + } + }, + "find-node-modules": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/find-node-modules/-/find-node-modules-1.0.4.tgz", + "integrity": "sha1-tt6zzMtpnIcDdne87eLF9YYrJVA=", + "dev": true, + "requires": { + "findup-sync": "0.4.2", + "merge": "^1.2.0" + } + }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "findup": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz", + "integrity": "sha1-itkpozk7rGJ5V6fl3kYjsGsOLOs=", + "dev": true, + "requires": { + "colors": "~0.6.0-1", + "commander": "~2.1.0" + }, + "dependencies": { + "colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", + "dev": true + }, + "commander": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", + "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=", + "dev": true + } + } + }, + "findup-sync": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.4.2.tgz", + "integrity": "sha1-qBF9D3MST1pFRoOVef5S1xKfteU=", + "dev": true, + "requires": { + "detect-file": "^0.1.0", + "is-glob": "^2.0.1", + "micromatch": "^2.3.7", + "resolve-dir": "^0.1.0" + }, + "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + } + } + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.0.tgz", + "integrity": "sha512-fdrt472/9qQ6Kgjvb935ig6vJCuofpBUD14f9Vb+SLlm7xIe4Qva5gey8EKtv8lp7ahE1wilg3xL1znpVGtZIA==", + "dev": true, + "requires": { + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "dev": true, + "requires": { + "null-check": "^1.0.0" + } + }, + "fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", + "dev": true + }, + "fs-extra": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz", + "integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.2.4", + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "bundled": true, + "dev": true, + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "1.0.2", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.2", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "optional": true, + "requires": { + "globule": "^1.0.0" + } + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "get-pkg-repo": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz", + "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "meow": "^3.3.0", + "normalize-package-data": "^2.3.0", + "parse-github-repo-url": "^1.3.0", + "through2": "^2.0.0" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "git-describe": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/git-describe/-/git-describe-4.0.3.tgz", + "integrity": "sha1-2BSCqK2nodgeBg1DisjhG0o5AzY=", + "requires": { + "lodash": "^4.16.6", + "semver": "^5.3.0" + } + }, + "git-raw-commits": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-1.3.6.tgz", + "integrity": "sha512-svsK26tQ8vEKnMshTDatSIQSMDdz8CxIIqKsvPqbtV23Etmw6VNaFAitu8zwZ0VrOne7FztwPyRLxK7/DIUTQg==", + "dev": true, + "requires": { + "dargs": "^4.0.1", + "lodash.template": "^4.0.2", + "meow": "^4.0.0", + "split2": "^2.0.0", + "through2": "^2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + } + } + }, + "git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "dev": true, + "requires": { + "gitconfiglocal": "^1.0.0", + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "git-semver-tags": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-1.3.6.tgz", + "integrity": "sha512-2jHlJnln4D/ECk9FxGEBh3k44wgYdWjWDtMmJPaecjoRmxKo3Y1Lh8GMYuOPu04CHw86NTAODchYjC5pnpMQig==", + "dev": true, + "requires": { + "meow": "^4.0.0", + "semver": "^5.5.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, + "meow": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", + "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist": "^1.1.3", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + } + } + }, + "gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", + "dev": true, + "requires": { + "ini": "^1.3.2" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", + "dev": true, + "requires": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "dependencies": { + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", + "dev": true + } + } + }, + "global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "dependencies": { + "is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", + "dev": true + } + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "optional": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + }, + "handle-thing": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "dev": true, + "requires": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "optional": true + } + } + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-binary": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "optional": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-entities": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true + }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + } + } + }, + "html-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "dev": true, + "requires": { + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", + "util.promisify": "1.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "http-parser-js": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "dev": true + }, + "http-proxy": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "dev": true, + "requires": { + "eventemitter3": "^3.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", + "dev": true, + "requires": { + "http-proxy": "^1.16.2", + "is-glob": "^4.0.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "in-publish": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", + "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==", + "dev": true, + "optional": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inquirer": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "internal-ip": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz", + "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", + "dev": true, + "requires": { + "default-gateway": "^2.6.0", + "ipaddr.js": "^1.5.2" + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-odd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-subset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", + "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "dev": true, + "requires": { + "text-extensions": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-api": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.1.tgz", + "integrity": "sha512-duj6AlLcsWNwUpfyfHt0nWIeRiZpuShnP40YTxOGQgtaN8fd6JYSxsvxUphTDy8V5MfDXo4s/xVCIIvVCO808g==", + "dev": true, + "requires": { + "async": "^2.1.4", + "compare-versions": "^3.1.0", + "fileset": "^2.0.2", + "istanbul-lib-coverage": "^1.2.0", + "istanbul-lib-hook": "^1.2.0", + "istanbul-lib-instrument": "^1.10.1", + "istanbul-lib-report": "^1.1.4", + "istanbul-lib-source-maps": "^1.2.4", + "istanbul-reports": "^1.3.0", + "js-yaml": "^3.7.0", + "mkdirp": "^0.5.1", + "once": "^1.4.0" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + } + } + }, + "istanbul-instrumenter-loader": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", + "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", + "dev": true, + "requires": { + "convert-source-map": "^1.5.0", + "istanbul-lib-instrument": "^1.7.3", + "loader-utils": "^1.1.0", + "schema-utils": "^0.3.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "schema-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", + "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", + "dev": true, + "requires": { + "ajv": "^5.0.0" + } + } + } + }, + "istanbul-lib-coverage": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", + "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.1.tgz", + "integrity": "sha512-eLAMkPG9FU0v5L02lIkcj/2/Zlz9OuluaXikdr5iStk8FDbSwAixTK9TkYxbF0eNnzAJTwM2fkV2A1tpsIp4Jg==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", + "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.0", + "semver": "^5.3.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz", + "integrity": "sha512-Azqvq5tT0U09nrncK3q82e/Zjkxa4tkFZv7E6VcqP0QCPn6oNljDPfrZEC/umNXds2t7b8sRJfs6Kmpzt8m2kA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.5.tgz", + "integrity": "sha512-8O2T/3VhrQHn0XcJbP1/GN7kXMiRAlPi+fj3uEHrjBD8Oz7Py0prSC25C09NuAZS6bgW1NNKAvCSHZXB0irSGA==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "istanbul-reports": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.3.0.tgz", + "integrity": "sha512-y2Z2IMqE1gefWUaVjrBm0mSKvUkaBy9Vqz8iwr/r40Y9hBbIteH5wqHG/9DLTfJ9xUnUT2j7A3+VVJ6EaYBllA==", + "dev": true, + "requires": { + "handlebars": "^4.0.3" + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + } + } + }, + "jasmine-core": { + "version": "2.99.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", + "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", + "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", + "dev": true, + "requires": { + "colors": "1.1.2" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true + }, + "jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "peer": true + }, + "js-base64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", + "dev": true, + "optional": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz", + "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", + "dev": true, + "requires": { + "core-js": "~2.3.0", + "es6-promise": "~3.0.2", + "lie": "~3.1.0", + "pako": "~1.0.2", + "readable-stream": "~2.0.6" + }, + "dependencies": { + "core-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", + "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=", + "dev": true + }, + "es6-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "karma": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz", + "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==", + "dev": true, + "requires": { + "bluebird": "^3.3.0", + "body-parser": "^1.16.1", + "chokidar": "^1.4.1", + "colors": "^1.1.0", + "combine-lists": "^1.0.0", + "connect": "^3.6.0", + "core-js": "^2.2.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.0", + "expand-braces": "^0.1.1", + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "http-proxy": "^1.13.0", + "isbinaryfile": "^3.0.0", + "lodash": "^3.8.0", + "log4js": "^0.6.31", + "mime": "^1.3.4", + "minimatch": "^3.0.2", + "optimist": "^0.6.1", + "qjobs": "^1.1.4", + "range-parser": "^1.2.0", + "rimraf": "^2.6.0", + "safe-buffer": "^5.0.1", + "socket.io": "1.7.3", + "source-map": "^0.5.3", + "tmp": "0.0.31", + "useragent": "^2.1.12" + }, + "dependencies": { + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + } + } + }, + "karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, + "requires": { + "fs-access": "^1.0.0", + "which": "^1.2.1" + } + }, + "karma-coverage-istanbul-reporter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.0.1.tgz", + "integrity": "sha512-UcgrHkFehI5+ivMouD8NH/UOHiX4oCAtwaANylzPFdcAuD52fnCUuelacq2gh8tZ4ydhU3+xiXofSq7j5Ehygw==", + "dev": true, + "requires": { + "istanbul-api": "^1.3.1", + "minimatch": "^3.0.4" + } + }, + "karma-edge-launcher": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/karma-edge-launcher/-/karma-edge-launcher-0.4.2.tgz", + "integrity": "sha512-YAJZb1fmRcxNhMIWYsjLuxwODBjh2cSHgTW/jkVmdpGguJjLbs9ZgIK/tEJsMQcBLUkO+yO4LBbqYxqgGW2HIw==", + "dev": true, + "requires": { + "edge-launcher": "1.2.2" + } + }, + "karma-firefox-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz", + "integrity": "sha512-LbZ5/XlIXLeQ3cqnCbYLn+rOVhuMIK9aZwlP6eOLGzWdo1UVp7t6CN3DP4SafiRLjexKwHeKHDm0c38Mtd3VxA==", + "dev": true + }, + "karma-ie-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz", + "integrity": "sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw=", + "dev": true, + "requires": { + "lodash": "^4.6.1" + } + }, + "karma-jasmine": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz", + "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=", + "dev": true, + "requires": {} + }, + "karma-jasmine-html-reporter": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz", + "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=", + "dev": true, + "requires": { + "karma-jasmine": "^1.0.2" + } + }, + "karma-junit-reporter": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz", + "integrity": "sha1-T5xAzt+xo5X4rvh2q/lhiZF8Y5Y=", + "dev": true, + "requires": { + "path-is-absolute": "^1.0.0", + "xmlbuilder": "8.2.2" + }, + "dependencies": { + "xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", + "dev": true + } + } + }, + "karma-source-map-support": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.3.0.tgz", + "integrity": "sha512-HcPqdAusNez/ywa+biN4EphGz62MmQyPggUsDfsHqa7tSe4jdsxgvTKuDfIazjL+IOxpVWyT7Pr4dhAV+sxX5Q==", + "dev": true, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "karma-spec-reporter": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.36.tgz", + "integrity": "sha512-11bvOl1x6ryKZph7kmbmMpbi8vsngEGxGOoeTlIcDaH3ab3j8aPJnZ+r+K/SS0sBSGy5VGkGYO2+hLct7hw/6w==", + "dev": true, + "requires": { + "colors": "1.4.0" + }, + "dependencies": { + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + } + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "less": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/less/-/less-3.8.1.tgz", + "integrity": "sha512-8HFGuWmL3FhQR0aH89escFNBQH/nEiYPP2ltDFdQw2chE28Yx2E3lhAIq9Y2saYwLSwa699s4dBVEfCY8Drf7Q==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "less-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz", + "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^3.0.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "license-webpack-plugin": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-1.5.0.tgz", + "integrity": "sha512-Of/H79rZqm2aeg4RnP9SMSh19qkKemoLT5VaJV58uH5AxeYWEcBgGFs753JEJ/Hm6BPvQVfIlrrjoBwYj8p7Tw==", + "dev": true, + "requires": { + "ejs": "^2.5.7" + } + }, + "lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "dev": true + }, + "lodash.template": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", + "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", + "dev": true, + "requires": { + "lodash._reinterpolate": "~3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", + "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", + "dev": true, + "requires": { + "lodash._reinterpolate": "~3.0.0" + } + }, + "log4js": { + "version": "0.6.38", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", + "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", + "dev": true, + "requires": { + "readable-stream": "~1.0.2", + "semver": "~4.3.3" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "loglevel": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", + "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", + "dev": true + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, + "requires": { + "js-tokens": "^3.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-error": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", + "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "dev": true + }, + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "merge": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz", + "integrity": "sha1-dTHjnUlJwoGma4xabgJl6LBYlNo=", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "microsoft-adal-angular6": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/microsoft-adal-angular6/-/microsoft-adal-angular6-1.0.0.tgz", + "integrity": "sha512-AU5/WfeHfyb/YhSjewaS/Cqr/SttV1msg33yReH/7B3EtBwlI3+logvo3ez6wJe37D/TYHtxXaoHsX8fYQOcxg==", + "requires": { + "@types/adal": "^1.0.29", + "adal-angular": "^1.0.15", + "tslib": "^1.9.0" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.4.tgz", + "integrity": "sha512-o+Jm+ocb0asEngdM6FsZWtZsRzA8koFUudIDwYUfl94M3PejPHG7Vopw5hN9V8WsMkSFpm3tZP3Fesz89EyrfQ==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", + "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-odd": "^2.0.0", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "ng-bullet": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ng-bullet/-/ng-bullet-1.0.3.tgz", + "integrity": "sha512-qacsE/w/pLlBxebort1rkrE2B4Vc3idutcpe7tYiHVarz0V6Q5SN8E3d6NUp4UFBMOucpHlCpaASp7qEOsxM1Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "node-forge": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", + "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "dev": true + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "npm-package-arg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", + "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-registry-client": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.5.1.tgz", + "integrity": "sha512-7rjGF2eA7hKDidGyEWmHTiKfXkbrcQAsGL/Rh4Rt3x3YNRNHhwaTzVJfW3aNvvlhg4G62VCluif0sLCb/i51Hg==", + "dev": true, + "requires": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", + "dev": true + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", + "dev": true + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-github-repo-url": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", + "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=", + "dev": true + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "parsejson": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", + "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dev": true, + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "peer": true + }, + "portfinder": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.19.tgz", + "integrity": "sha512-23aeQKW9KgHe6citUrG3r9HjeX6vls0h713TAa+CwTKZwNIr/pD2ApaxYF4Um3ZZyq4ar+Siv3+fhoHaIwSOSw==", + "dev": true, + "requires": { + "async": "^1.5.2", + "debug": "^2.2.0", + "mkdirp": "0.5.x" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "postcss-import": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-11.1.0.tgz", + "integrity": "sha512-5l327iI75POonjxkXgdRCUS+AlzAdBx4pOvMEhTKTCjb1p8IEeVR9yx3cPbmN7LIWJLbfnIXxAhoB4jpD0c/Cw==", + "dev": true, + "requires": { + "postcss": "^6.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.6.tgz", + "integrity": "sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^6.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^0.4.0" + } + }, + "postcss-url": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-7.3.2.tgz", + "integrity": "sha512-QMV5mA+pCYZQcUEPQkmor9vcPQ2MT+Ipuu8qdi1gVxbNiIiErEGft+eny1ak19qALoBkccS5AHaCaCDzh7b9MA==", + "dev": true, + "requires": { + "mime": "^1.4.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.0", + "postcss": "^6.0.1", + "xxhashjs": "^0.2.1" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "optional": true, + "requires": { + "asap": "~2.0.3" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "protractor": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.3.2.tgz", + "integrity": "sha512-pw4uwwiy5lHZjIguxNpkEwJJa7hVz+bJsvaTI+IbXlfn2qXwzbF8eghW/RmrZwE2sGx82I8etb8lVjQ+JrjejA==", + "dev": true, + "requires": { + "@types/node": "^6.0.46", + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "~2.53.39", + "blocking-proxy": "^1.0.0", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "optimist": "~0.6.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "^1.0.0", + "webdriver-manager": "^12.0.6" + }, + "dependencies": { + "@types/node": { + "version": "6.0.112", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.112.tgz", + "integrity": "sha512-HnekQWq9t3Gl5aBYYi8sGyOWm85M5ub2llMrpQkRY94eJEUhsUr8qYNaeefv22cxxm+D67a+5zIzpl+dpFxdjQ==", + "dev": true + }, + "adm-zip": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz", + "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "webdriver-manager": { + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.0.6.tgz", + "integrity": "sha1-PfGkgZdwELTL+MnYXHpXeCjA5ws=", + "dev": true, + "requires": { + "adm-zip": "^0.4.7", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.78.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + } + } + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "puppeteer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.5.0.tgz", + "integrity": "sha512-eELwFtFxL+uhmg4jPZOZXzSrPEYy4CaYQNbcchBbfxY+KjMpnv6XGf/aYWaQG49OTpfi2/DMziXtDM8XuJgoUA==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "extract-zip": "^1.6.6", + "https-proxy-agent": "^2.2.1", + "mime": "^2.0.3", + "progress": "^2.0.0", + "proxy-from-env": "^1.0.0", + "rimraf": "^2.6.1", + "ws": "^5.1.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "dev": true + }, + "ws": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.1.tgz", + "integrity": "sha512-2NkHdPKjDBj3CHdnAGNpmlliryKqF+n9MYXX7/wsVC4yqYocKreKNjydPDvT3wShAZnndlM0RytEfTALCDvz7A==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "dev": true + }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, + "randomatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", + "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", + "dev": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "dev": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + } + } + }, + "raw-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "dev": true + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "dependencies": { + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "reflect-metadata": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==", + "dev": true + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "renderkid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", + "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + } + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", + "dev": true, + "requires": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "right-pad": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/right-pad/-/right-pad-1.0.1.tgz", + "integrity": "sha1-jKCMLLtbVedNr6lr9/0aJ9VoyNA=", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rxjs": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.1.tgz", + "integrity": "sha512-OwMxHxmnmHTUpgO+V7dZChf3Tixf4ih95cmXjzzadULziVl/FKhHScGLj4goEw9weePVOH2Q0+GcCBUhKCZc/g==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass": { + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "dev": true, + "requires": { + "clone-deep": "^2.0.1", + "loader-utils": "^1.0.1", + "lodash.tail": "^4.1.1", + "neo-async": "^2.5.0", + "pify": "^3.0.0", + "semver": "^5.5.0" + } + }, + "saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + } + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "dev": true + }, + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "dependencies": { + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + } + } + }, + "selfsigned": { + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", + "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "dev": true, + "requires": { + "node-forge": "0.9.0" + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "devOptional": true + }, + "semver-dsl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", + "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", + "dev": true, + "requires": { + "semver": "^5.3.0" + } + }, + "semver-intersect": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.3.1.tgz", + "integrity": "sha1-j6hKnhAovSOeRTDRo+GB5pjYhLo=", + "dev": true, + "requires": { + "semver": "^5.0.0" + } + }, + "semver-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz", + "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=", + "dev": true + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "dependencies": { + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + } + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^5.0.0", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shelljs": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", + "integrity": "sha1-N5zM+1a5HIYB5HkzVutTgpJN6a0=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "silent-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/silent-error/-/silent-error-1.1.0.tgz", + "integrity": "sha1-IglwbxyFCp8dENDYQJGLRvJuG8k=", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.3.tgz", + "integrity": "sha1-uK+cq6AJSeVo42nxMn6pvp6iRhs=", + "dev": true, + "requires": { + "debug": "2.3.3", + "engine.io": "1.8.3", + "has-binary": "0.1.7", + "object-assign": "4.1.0", + "socket.io-adapter": "0.5.0", + "socket.io-client": "1.7.3", + "socket.io-parser": "2.3.1" + }, + "dependencies": { + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "object-assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", + "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", + "dev": true + } + } + }, + "socket.io-adapter": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", + "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=", + "dev": true, + "requires": { + "debug": "2.3.3", + "socket.io-parser": "2.3.1" + }, + "dependencies": { + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-client": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.3.tgz", + "integrity": "sha1-sw6GqhDV7zVGYBwJzeR2Xjgdo3c=", + "dev": true, + "requires": { + "backo2": "1.0.2", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "2.3.3", + "engine.io-client": "1.8.3", + "has-binary": "0.1.7", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseuri": "0.0.5", + "socket.io-parser": "2.3.1", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", + "dev": true, + "requires": { + "ms": "0.7.2" + } + }, + "ms": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + } + } + }, + "socket.io-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", + "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=", + "dev": true, + "requires": { + "component-emitter": "1.1.2", + "debug": "2.2.0", + "isarray": "0.0.1", + "json3": "3.3.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", + "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=", + "dev": true + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + } + } + }, + "sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "requires": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + } + } + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", + "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "dev": true + }, + "spdy": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", + "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "dev": true, + "requires": { + "through2": "^2.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "standard-version": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-4.4.0.tgz", + "integrity": "sha512-jJ8FZhnmh9xJRQLnaXiGRLaAUNItIH29lOQZGpL5fd4+jUHto9Ij6SPCYN86h6ZNNXkYq2TYiIVVF7gVyC+pcQ==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "conventional-changelog": "^1.1.0", + "conventional-recommended-bump": "^1.0.0", + "dotgitignore": "^1.0.3", + "figures": "^1.5.0", + "fs-access": "^1.0.0", + "semver": "^5.1.0", + "yargs": "^8.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", + "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stats-webpack-plugin": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/stats-webpack-plugin/-/stats-webpack-plugin-0.6.2.tgz", + "integrity": "sha1-LFlJtTHgf4eojm6k3PrFOqjHWis=", + "dev": true, + "requires": { + "lodash": "^4.17.4" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "optional": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz", + "integrity": "sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^0.4.5" + } + }, + "stylus": { + "version": "0.54.5", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz", + "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=", + "dev": true, + "requires": { + "css-parse": "1.7.x", + "debug": "*", + "glob": "7.0.x", + "mkdirp": "0.5.x", + "sax": "0.5.x", + "source-map": "0.1.x" + }, + "dependencies": { + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "stylus-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz", + "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==", + "dev": true, + "requires": { + "loader-utils": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "when": "~3.6.x" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true + }, + "text-mask-addons": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/text-mask-addons/-/text-mask-addons-3.7.2.tgz", + "integrity": "sha512-uezfAuWcDJ/B/lu5ooz38DEdI8NfIVX2xKwshLs40Er8MBGSppklOqOYw32wEmCUP7XPVX2u3u2TdQcIRuBLXA==" + }, + "text-mask-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/text-mask-core/-/text-mask-core-5.1.1.tgz", + "integrity": "sha512-tcHJSs0jlHpIed2flCuZZhUIGN1KfDsN/qCW7lkYxyXkKt4OLd3GXCHPFsY9rOLhmyC43ZRTM+tCxizBzluWpw==" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tree-kill": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-off-newlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.2" + } + }, + "ts-node": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-5.0.1.tgz", + "integrity": "sha512-XK7QmDcNHVmZkVtkiwNDWiERRHPyU8nBqZB1+iv2UhOG0q3RQ9HsZ2CMqISlFbxjrYFGfG2mX7bW4dAyxBVzUw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "chalk": "^2.3.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.3", + "yn": "^2.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "tsickle": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.29.0.tgz", + "integrity": "sha512-JpID0Lv8/irRtPmqJJxb5fCwfZhjZeKmav9Zna7UjqVuJoSbI49Wue/c2PPybX1SbRrjl7bbI/JsCl0dSUJygA==", + "dev": true, + "requires": { + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map": "^0.6.0", + "source-map-support": "^0.5.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "tslib": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz", + "integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw==" + }, + "tslint": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", + "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.12.1" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "tsutils": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", + "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", + "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", + "dev": true + }, + "uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "uglifyjs-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", + "dev": true, + "requires": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "dev": true, + "requires": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + } + } + } + }, + "ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", + "dev": true + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true + }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-loader": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", + "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", + "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "dev": true, + "requires": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + } + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "validate-commit-msg": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/validate-commit-msg/-/validate-commit-msg-2.14.0.tgz", + "integrity": "sha1-5Tg2kQEsuycNzAvCpO/+vhSJDqw=", + "dev": true, + "requires": { + "conventional-commit-types": "^2.0.0", + "find-parent-dir": "^0.3.0", + "findup": "0.1.5", + "semver-regex": "1.0.0" + } + }, + "validate-npm-package-license": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "webdriver-js-extender": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-1.0.0.tgz", + "integrity": "sha1-gcUzqeM9W/tZe05j4s2yW1R3dRU=", + "dev": true, + "requires": { + "@types/selenium-webdriver": "^2.53.35", + "selenium-webdriver": "^2.53.2" + }, + "dependencies": { + "sax": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.6.1.tgz", + "integrity": "sha1-VjsZx8HeiS4Jv8Ty/DDjwn8JUrk=", + "dev": true + }, + "selenium-webdriver": { + "version": "2.53.3", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-2.53.3.tgz", + "integrity": "sha1-0p/1qVff8aG0ncRXdW5OS/vc4IU=", + "dev": true, + "requires": { + "adm-zip": "0.4.4", + "rimraf": "^2.2.8", + "tmp": "0.0.24", + "ws": "^1.0.1", + "xml2js": "0.4.4" + } + }, + "tmp": { + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.24.tgz", + "integrity": "sha1-1qXhmNFKmDXMby18PZ4wJCjIzxI=", + "dev": true + }, + "xml2js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.4.tgz", + "integrity": "sha1-MREBAAMAiuGSQOuhdJe1fHKcVV0=", + "dev": true, + "requires": { + "sax": "0.6.x", + "xmlbuilder": ">=1.0.0" + } + } + } + }, + "webpack": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.4.tgz", + "integrity": "sha512-RqUfwp4qMqv3oFwBQQOoK69C2tdu2FHJEqPABPqgjGDvOIOLqkTOhmmdJjpiRabzNAAH1ahmkA3z4xowlHN+VA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/wasm-edit": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "acorn": "^5.6.2", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.0.0", + "uglifyjs-webpack-plugin": "^1.2.4", + "watchpack": "^1.5.0", + "webpack-sources": "^1.0.1" + } + }, + "webpack-core": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", + "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", + "dev": true, + "requires": { + "source-list-map": "~0.1.7", + "source-map": "~0.4.1" + }, + "dependencies": { + "source-list-map": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", + "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", + "dev": true + }, + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz", + "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", + "dev": true, + "requires": { + "memory-fs": "~0.4.1", + "mime": "^2.3.1", + "range-parser": "^1.0.3", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz", + "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^3.1.0", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "~0.18.0", + "import-local": "^2.0.0", + "internal-ip": "^3.0.1", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "schema-utils": "^1.0.0", + "selfsigned": "^1.9.1", + "semver": "^5.6.0", + "serve-index": "^1.7.2", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^5.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "3.4.0", + "webpack-log": "^2.0.0", + "yargs": "12.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "requires": { + "xregexp": "4.0.0" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "yargs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^2.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^10.1.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-merge": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.4.tgz", + "integrity": "sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ==", + "dev": true, + "requires": { + "lodash": "^4.17.5" + } + }, + "webpack-sources": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "webpack-subresource-integrity": { + "version": "1.1.0-rc.4", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.4.tgz", + "integrity": "sha1-xcTj1pD50vZKlVDgeodn+Xlqpdg=", + "dev": true, + "requires": { + "webpack-core": "^0.6.8" + } + }, + "websocket-driver": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", + "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.4.0 <0.4.11", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true + }, + "when": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz", + "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "optional": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz", + "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=", + "dev": true, + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + }, + "wtf-8": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", + "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + }, + "dependencies": { + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + } + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "xmlhttprequest-ssl": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", + "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", + "dev": true + }, + "xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "requires": { + "cuint": "^0.2.2" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "optional": true + } + } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "~1.0.1" + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", + "dev": true + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + }, + "zone.js": { + "version": "0.8.26", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.26.tgz", + "integrity": "sha512-W9Nj+UmBJG251wkCacIkETgra4QgBo/vgoEkb4a2uoLzpQG7qF9nzwoLXWU5xj3Fg2mxGvEDh47mg24vXccYjA==" + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/package.json b/LCU.WebApps.External.OnlineLoanApp/package.json new file mode 100644 index 0000000..ec005f6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/package.json @@ -0,0 +1,106 @@ +{ + "name": "ui", + "version": "4.11.0", + "scripts": { + "ng": "ng", + "update-e2e": "webdriver-manager update", + "start": "SET NODE_OPTIONS=--openssl-legacy-provider && ng serve --proxy-config proxy.conf.json --configuration=local --ssl", + "build-develop": "SET NODE_OPTIONS=--openssl-legacy-provider && ng build --configuration=dev", + "build-release": "SET NODE_OPTIONS=--openssl-legacy-provider && ng build --configuration=qa", + "build-master": "SET NODE_OPTIONS=--openssl-legacy-provider && ng build --prod --build-optimizer", + "test": "ng test --watch=false --code-coverage --no-progress", + "test-debug": "ng test --watch=false --source-map=false --no-progress", + "test-interactive": "ng test --watch=false --browsers=Chrome --no-progress", + "lint": "ng lint", + "e2e": "ng e2e", + "postinstall": "node version.js", + "commitmsg": "validate-commit-msg", + "release": "standard-version" + }, + "private": true, + "dependencies": { + "@angular/animations": "^6.0.3", + "@angular/cdk": "6.4.7", + "@angular/common": "^6.0.3", + "@angular/compiler": "^6.0.3", + "@angular/core": "^6.0.3", + "@angular/forms": "^6.0.3", + "@angular/http": "^6.0.3", + "@angular/material": "6.4.7", + "@angular/platform-browser": "^6.0.3", + "@angular/platform-browser-dynamic": "^6.0.3", + "@angular/router": "^6.0.3", + "@fortawesome/fontawesome": "^1.1.8", + "@fortawesome/fontawesome-free-regular": "^5.0.13", + "@fortawesome/fontawesome-free-solid": "^5.0.13", + "@ng-bootstrap/ng-bootstrap": "^2.1.0", + "@ngx-translate/core": "^10.0.2", + "@ngx-translate/http-loader": "^3.0.1", + "angular2-text-mask": "^9.0.0", + "bootstrap": "^4.1.1", + "core-js": "^2.5.4", + "file-saver": "^2.0.0", + "fs-extra": "^7.0.0", + "git-describe": "^4.0.3", + "microsoft-adal-angular6": "^1.0.0", + "rxjs": "^6.0.0", + "text-mask-addons": "^3.7.2", + "zone.js": "^0.8.26" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~0.8.9", + "@angular/cli": "~6.0.8", + "@angular/compiler-cli": "^6.0.3", + "@angular/language-service": "^6.0.3", + "@types/jasmine": "~2.8.6", + "@types/jasminewd2": "~2.0.3", + "@types/node": "~8.9.4", + "codelyzer": "~4.2.1", + "commitizen": "^3.0.4", + "cz-conventional-changelog": "^2.1.0", + "jasmine-core": "~2.99.1", + "jasmine-spec-reporter": "~4.2.1", + "karma": "~1.7.1", + "karma-chrome-launcher": "^2.2.0", + "karma-coverage-istanbul-reporter": "~2.0.0", + "karma-spec-reporter": "^0.0.36", + "karma-edge-launcher": "^0.4.2", + "karma-firefox-launcher": "^1.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-jasmine": "~1.1.1", + "karma-jasmine-html-reporter": "^0.2.2", + "karma-junit-reporter": "^1.2.0", + "ng-bullet": "^1.0.3", + "protractor": "~5.3.0", + "puppeteer": "^1.5.0", + "sass": "^1.49.9", + "standard-version": "^4.4.0", + "ts-node": "~5.0.1", + "tslint": "~5.9.1", + "typescript": "~2.7.2", + "validate-commit-msg": "^2.14.0" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + }, + "validate-commit-msg": { + "types": [ + "feat", + "fix", + "docs", + "style", + "refactor", + "perf", + "test", + "chore", + "revert" + ], + "warnOnFail": false, + "maxSubjectLength": 100, + "subjectPattern": "^.*", + "subjectPatternErrorMsg": "Subject must be in format 'Commit message'", + "helpMessage": "" + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/setup-env.ps1 b/LCU.WebApps.External.OnlineLoanApp/setup-env.ps1 new file mode 100644 index 0000000..e78587a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/setup-env.ps1 @@ -0,0 +1,44 @@ +# Landmark Credit Union +# Loan Wizard Environment Setup +# Running this script on a Windows 10 machine should install all of the software you need +# to work on the project for both the client and api. +# You can use 'choco upgrade all' to keep software up to date. + +Write-Output "Starting environment setup, if you use Visual Studio, or Webstorm, install those separately" + +# 1. Create an update function. +function Update-Environment-Path +{ + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") ` + + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") +} + +# 2. Get the Choco package manager, to enable the rest of this script +if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit } +Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | iex +Update-Environment-Path + +# 3. Git for source code management. +choco install git --yes --params '/GitAndUnixToolsOnPath' +Update-Environment-Path + +# 4. Node for node and npm. +# 10.14.1 to avoid e2e problems +choco install nodejs --version 10.14.1 --yes +Update-Environment-Path + +# 5. VS Code +choco install visualstudiocode --yes # includes dotnet +Update-Environment-Path +code --install-extension CoenraadS.bracket-pair-colorizer +code --install-extension ms-vscode.PowerShell +code --install-extension ms-vscode.csharp +code --install-extension eg2.vscode-npm-script +code --install-extension christian-kohler.npm-intellisense + +# 6. major browsers +choco install googlechrome --yes +choco install firefox --yes +Update-Environment-Path + +Write-Output "Complete" diff --git a/LCU.WebApps.External.OnlineLoanApp/src/Robots.txt b/LCU.WebApps.External.OnlineLoanApp/src/Robots.txt new file mode 100644 index 0000000..c6742d8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/Robots.txt @@ -0,0 +1,2 @@ +User-Agent: * +Disallow: / diff --git a/LCU.WebApps.External.OnlineLoanApp/src/_variables.scss b/LCU.WebApps.External.OnlineLoanApp/src/_variables.scss new file mode 100644 index 0000000..45e6383 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/_variables.scss @@ -0,0 +1,6 @@ +$font-family-base: Arial, sans-serif; + +$primary: #005B9A; +$secondary: #7ca550; // #FCCA10; +$warning: #F5961E; +$body-bg: #FFFFFF; \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin-groups.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin-groups.ts new file mode 100644 index 0000000..da516da --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin-groups.ts @@ -0,0 +1,30 @@ +import { Validators } from '@angular/forms'; + + +export class AdminGroups { + private static DATE_PATTERN = /^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/; + + + public static readonly FILTER = { + group: 'filterInformation', + fields: [ + {field: 'startDate', validation: [Validators.required, Validators.pattern(AdminGroups.DATE_PATTERN)]}, + {field: 'endDate', validation: [Validators.required, Validators.pattern(AdminGroups.DATE_PATTERN)]}, + {field: 'loanType', validation: []}, + {field: 'branch', validation: []}, + {field: 'decisioned', validation: []}, + {field: 'submitted', validation: []}, + {field: 'inProgress', validation: []}, + {field: 'error', validation: []}, + {field: 'deleted', validation: []}, + ] + }; + + public static readonly CONFIG = { + group: 'configInformation', + fields: [ + {field: 'appStatus', validation: []}, + ] + }; + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin-routing.module.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin-routing.module.ts new file mode 100644 index 0000000..769a608 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin-routing.module.ts @@ -0,0 +1,31 @@ +import { RouterModule, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { AdminComponent } from './admin.component'; +import { LoanTableComponent } from './loan-table/loan-table.component'; +import { LoanDataComponent } from './loan-data/loan-data.component'; +import { BranchConfigComponent } from './branch-config/branch-config.component'; +import { BranchNameConfigComponent } from './branch-name-config/branch-name-config.component'; +import { SystemConfigComponent } from './system-config/system-config.component'; +import { SchumerPreviewComponent } from './schumer-boxes/schumer-preview.component'; + +const routes: Routes = [ + { + path: '', + component: AdminComponent, + children: [ + {path: 'branch-config', component: BranchConfigComponent}, + {path: 'branch-name-config', component: BranchNameConfigComponent}, + {path: 'loan-table', component: LoanTableComponent}, + {path: 'system-config', component: SystemConfigComponent}, + {path: 'schumer-preview', component: SchumerPreviewComponent}, + {path: 'loan-data/:id', component: LoanDataComponent}, + {path: '', redirectTo: '/admin', pathMatch: 'prefix'}, + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AdminRoutingModule {} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.html new file mode 100644 index 0000000..4e25ed4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.html @@ -0,0 +1,7 @@ +
+ +
+
+ +
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.ts new file mode 100644 index 0000000..7d738e0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'lw-admin', + templateUrl: './admin.component.html', + styleUrls: ['./admin.component.scss'] +}) +export class AdminComponent { + constructor(public router: Router ) {} +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.module.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.module.ts new file mode 100644 index 0000000..6316c04 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/admin.module.ts @@ -0,0 +1,68 @@ +import { NgModule } from '@angular/core'; +import { MatSortModule } from '@angular/material/sort'; +import { MatPaginatorModule, MatNativeDateModule, MatSelectModule, MatCheckboxModule, + MatTooltipModule, MatSnackBarModule, MatSlideToggleModule } from '@angular/material'; +import { MatTableModule } from '@angular/material/table'; +import { MatInputModule } from '@angular/material/input'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { AdminComponent } from './admin.component'; +import { LoanTableComponent } from './loan-table/loan-table.component'; +import { LoanDataComponent } from './loan-data/loan-data.component'; +import { AdminRoutingModule } from './admin-routing.module'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { BranchConfigComponent } from './branch-config/branch-config.component'; +import { BranchNameConfigComponent } from './branch-name-config/branch-name-config.component'; +import { AdminLandingComponent } from './landing/admin-landing.component'; +import { MainCardAdminComponent } from './cards/main-card/main-card.component'; +import { TopNavigationComponent } from './navigation/top-navigation.component'; +import { SharedModule } from '../shared/shared.module'; +import { SnackBarErrorComponent } from './snack-bar-error/snack-bar-error.component'; +import { SystemConfigComponent } from './system-config/system-config.component'; +import { SnackBarService } from './snack-bar-error/snack-bar.service'; +import { SchumerPreviewComponent } from './schumer-boxes/schumer-preview.component'; + +@NgModule({ + imports: [ + AdminRoutingModule, + CommonModule, + SharedModule, + FormsModule, + MatSortModule, + MatPaginatorModule, + MatTableModule, + MatInputModule, + MatTooltipModule, + MatDatepickerModule, + MatNativeDateModule, + MatSelectModule, + MatSnackBarModule, + MatCheckboxModule, + MatSlideToggleModule, + ReactiveFormsModule, + TranslateModule, + ], + entryComponents: [ + SnackBarErrorComponent + ], + declarations: [ + AdminComponent, + AdminLandingComponent, + BranchConfigComponent, + BranchNameConfigComponent, + LoanTableComponent, + LoanDataComponent, + SnackBarErrorComponent, + MainCardAdminComponent, + TopNavigationComponent, + SchumerPreviewComponent, + SystemConfigComponent + ], + providers: [ + MatDatepickerModule, + SnackBarService + ], +}) + +export class AdminModule {} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.html new file mode 100644 index 0000000..8d85b9e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.html @@ -0,0 +1,81 @@ + +
+ +
+

BRANCHMAP.TITLE

+ BRANCHMAP.SUBTEXT +

+ BRANCHMAP.DATETEXT{{lastUpdated | date:'short'}} +

+
+
+
+
+
+
+
BRANCHMAP.DOWNLOAD
+ +
+
+
+
+
+
+
+
+
+
BRANCHMAP.UPLOAD
+ +
+
+
+
+ +
+
+ +
+
+
+
+
Please confirm your upload
+
    +
  • There are currently {{localState.compareResult.currentCount}} branch mappings.
  • +
  • There will be {{localState.compareResult.newCount}} branch mappings.
  • +
  • {{warning}}
  • +
+ +
+
+
There was a problem with your upload, it will not be saved.
+
    +
  • {{localState.compareResult.errorMessage}}
  • +
  • {{warning}}
  • +
+
+
+
+
+ +
+ + + + + + + + + + + +
ZipCode{{loan.zipcode}} Channel ID{{loan.channelId}}
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.scss new file mode 100644 index 0000000..c76ae58 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.scss @@ -0,0 +1,38 @@ +table { + width: 100%; +} + +.mat-form-field { + margin-right: 10px; + font-size: .875em; + width: 20%; +} + +.mat-header-cell { + font-size: 1em; + font-weight: bold; + color: black; +} + +.mat-checkbox { + margin-right: 10px; + :host ::ng-deep .mat-checkbox-background { + background-color: #005B9A + } +} + +.btn-disabled { + margin-bottom: 0px; + margin-left: 0px; +} + +.btn-less-padding { + padding-top: 3px; + padding-bottom: 3px; + padding-left: 8px; + padding-right: 8px; +} + +.shift-right { + margin-left: 5px; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.spec.ts new file mode 100644 index 0000000..cc43d5a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.spec.ts @@ -0,0 +1,66 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { BranchConfigComponent } from './branch-config.component'; +import { MatSortModule, MatPaginatorModule, MatTableModule, MatInputModule, + MatDatepickerModule, MatNativeDateModule, MatSelectModule, MatCheckboxModule, MatSnackBar, MatSnackBarModule } from '@angular/material'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { LoanCodePipe } from 'src/app/shared/pipes/loan-code.pipe'; +import { BranchConfigService } from './branch-config.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TopNavigationComponent } from '../navigation/top-navigation.component'; +import { SnackBarErrorComponent } from '../snack-bar-error/snack-bar-error.component'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('BranchConfigComponent', () => { + let component: BranchConfigComponent; + let fixture: ComponentFixture; + let service: BranchConfigService; + let snackBar: MatSnackBar; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + FormsModule, + MatSortModule, + MatPaginatorModule, + MatTableModule, + MatInputModule, + MatSnackBarModule, + MatDatepickerModule, + MatNativeDateModule, + MatSelectModule, + MatCheckboxModule, + ReactiveFormsModule, + HttpClientTestingModule, + NoopAnimationsModule, + TranslateModule.forRoot() + ], + declarations: [ + SnackBarErrorComponent, + BranchConfigComponent, + LoanCodePipe, + TopNavigationComponent + ], + providers: [ + ] + }); + }); + + + beforeEach(() => { + service = TestBed.get(BranchConfigService); + snackBar = TestBed.get(MatSnackBar); + fixture = TestBed.createComponent(BranchConfigComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + expect(service).toBeTruthy(); + fixture.detectChanges(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.ts new file mode 100644 index 0000000..701f0d0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.component.ts @@ -0,0 +1,126 @@ +import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { BranchConfigService } from './branch-config.service'; +import { Subscription } from 'rxjs'; +import { MatTableDataSource, MatSort } from '@angular/material'; +import { BranchConfig } from './branch-config'; +import { ServerError } from '../snack-bar-error/server-error'; +import { SnackBarErrorComponent } from '../snack-bar-error/snack-bar-error.component'; +import { SnackBarService } from '../snack-bar-error/snack-bar.service'; + +@Component({ + selector: 'lw-branch-config', + templateUrl: './branch-config.component.html', + styleUrls: ['./branch-config.component.scss'] +}) +export class BranchConfigComponent implements OnInit, OnDestroy { + + private dataSubscription: Subscription; + public dataSource: MatTableDataSource; + public lastUpdated: Date; + public displayedColumns: string[] = ['zipcode', 'channelId']; + public localState = { + uploadConfirmed: false, + compareResult: null, + pendingData: null + }; + + @ViewChild(MatSort) sort: MatSort; + + @ViewChild('fileImportInput') fileImportInput: any; + + constructor(private branchConfigService: BranchConfigService, private snackBarService: SnackBarService) {} + + public ngOnInit() { + this.subscribeToSourceData(); + if (!this.branchConfigService.hasBranchConfig()) { + this.loadBranchConfig(); + } + } + + public loadBranchConfig(): void { + this.branchConfigService.loadBranchConfig().subscribe( + (success: BranchConfig) => { + this.branchConfigService.setBranchConfig(success); + }, + (error) => { + this.snackBarService.throwServerError(error, 'BRANCHMAP.LOAD_ERROR'); + } + ); + } + + public subscribeToSourceData(): void { + this.dataSubscription = this.branchConfigService.branchConfig$.subscribe((branchConfig) => { + if (branchConfig) { + this.dataSource = new MatTableDataSource(branchConfig.branchCodesByZip); + this.lastUpdated = branchConfig.date; + this.dataSource.sortingDataAccessor = (data, sortHeaderId) => data[sortHeaderId].toLocaleLowerCase(); + this.dataSource.sort = this.sort; + } + }); + } + + public commandExportCSV() { + this.branchConfigService.exportCSV(this.dataSource.data); + } + + public ngOnDestroy(): void { + if (this.dataSubscription) { + this.dataSubscription.unsubscribe(); + } + } + + public fileChangeListener($event): void { + const input = $event.target; + const currentFile = input.files[0]; + if (currentFile) { + const reader = new FileReader(); + reader.readAsText(currentFile); + + reader.onload = (data) => { + this.localState.pendingData = reader.result; + this.localState.compareResult = this.branchConfigService.validateUpload(this.localState.pendingData); + this.fileReset(); + }; + + reader.onerror = function () { + alert('Unable to read ' + currentFile); + }; + } + } + + public fileReset(): void { + this.fileImportInput.nativeElement.value = ''; + } + + public resetLocalState(): void { + this.localState = { + uploadConfirmed: false, + compareResult: null, + pendingData: null + }; + } + + public handleLocalStateError(errorResult): void { + this.localState.compareResult.warnings = []; + let field; + for (field in errorResult.error) { + if (errorResult.error.hasOwnProperty(field)) { + this.localState.compareResult.warnings.push(field + ': ' + errorResult.error[field][0]); + } + } + this.localState.compareResult.inError = true; + this.localState.compareResult.errorMessage = 'The server rejected the mapping.'; + } + + public commandConfirmSave(): void { + this.branchConfigService.saveNewBranchMapping(this.localState.compareResult.convertedData).subscribe( + (result) => { + this.resetLocalState(); + }, + (errorResult) => { + this.snackBarService.throwServerError(errorResult, 'BRANCHMAP.LOAD_ERROR'); + this.handleLocalStateError(errorResult); + } + ); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.service.spec.ts new file mode 100644 index 0000000..7c878fb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.service.spec.ts @@ -0,0 +1,175 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { configureTestSuite } from 'ng-bullet'; +import { BranchConfigService } from './branch-config.service'; +import { environment } from 'src/environments/environment'; +import { FilterUtil } from '../filter/filter-util'; +import { Filter } from '../filter/filter'; +import { BranchConfig, BranchData } from './branch-config'; + + +describe('BranchConfig Service', () => { + + let service: BranchConfigService; + let httpTestingController: HttpTestingController; + const urlBase: string = environment.urlBase; + const baseConfig = { + id: '5', + date: new Date(), + branchCodesByZip: [ + { + zipcode: '53207', + channelId: 9196721 + }, + { + zipcode: '53209', + channelId: 9196721 + }, + { + zipcode: '53215', + channelId: 9196722 + } + ] + }; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + BranchConfigService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(BranchConfigService); + httpTestingController = TestBed.get(HttpTestingController); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should build branch config url', () => { + const url = service.buildBranchConfigURL(); + expect(url).toBe(urlBase + '/BranchConfig'); + }); + + it('should identify if there is a branch config', () => { + const apps = service.hasBranchConfig(); + expect(apps).toEqual(false); + }); + + it('should load branch config', () => { + const testBranchConfig = new BranchConfig(); + testBranchConfig.branchCodesByZip = []; + testBranchConfig.date = new Date(); + testBranchConfig.id = '555'; + + service.loadBranchConfig().subscribe((data) => { + expect(data).toEqual(testBranchConfig); + service.setBranchConfig(data); + } + ); + const req = httpTestingController.expectOne(service.buildBranchConfigURL()); + + // Assert that the request is a GET. + expect(req.request.method).toEqual('GET'); + + // Assert that the request contains the appropriate header. + expect(req.request.headers).toEqual(new HttpHeaders().set('Authorization', 'Bearer ' + localStorage.getItem('adal.idtoken'))); + + // Respond with mock data, causing Observable to resolve. + // Subscribe callback asserts that correct data was returned. + req.flush(testBranchConfig); + + // Finally, assert that there are no outstanding requests. + httpTestingController.verify(); + }); + + it('should save a branch config', () => { + const testBranchConfig = new BranchConfig(); + + service.submitBranchConfig(testBranchConfig).subscribe(() => {}); + const req = httpTestingController.expectOne(service.buildBranchConfigURL()); + expect(req.request.method).toEqual('PUT'); + httpTestingController.verify(); + }); + + it('should validate an upload.', () => { + service.setBranchConfig(baseConfig); + const newConfig = + `ZIP,ChannelId + 53207,9196722 + 53209,9196721 + 53215,9196722 + `; + + const result = service.validateUpload(newConfig); + expect(result.inError).toEqual(false); + expect(result.currentCount).toEqual(baseConfig.branchCodesByZip.length); + expect(result.newCount).toEqual(3); + expect(result.warnings.length).toEqual(1); + }); + + it('should check for removed entries in an upload.', () => { + service.setBranchConfig(baseConfig); + expect(service.checkForRemovedEntries(baseConfig.branchCodesByZip, baseConfig.branchCodesByZip)).toEqual([]); + const newConfig = { + id: '5', + branchCodesByZip: [ + { + zipcode: '53207', + channelId: 9196721 + }, + { + zipcode: '53215', + channelId: 9196722 + } + ] + }; + expect(service.checkForRemovedEntries(baseConfig.branchCodesByZip, newConfig.branchCodesByZip)).toEqual([ + 'Zipcode 53209 is not in the new set.' + ]); + + }); + + it('should create branch data from a row of an upload.', () => { + service.setBranchConfig(baseConfig); + expect(service.createBranchDataFromRow('53215,9196722', 1)).toEqual( + { + zipcode: '53215', + channelId: 9196722 + } + ); + expect(() => { service.createBranchDataFromRow('53215', 0); }).toThrow(); + expect(service.createBranchDataFromRow('', 1)).toBeNull(); + }); + + it('should find existing rows given an uploaded row. ', () => { + service.setBranchConfig(baseConfig); + expect(service.findExistingRow(baseConfig.branchCodesByZip, { + zipcode: '53215', + channelId: 9196725 + })).toEqual({ + zipcode: '53215', + channelId: 9196722 + } + ); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.service.ts new file mode 100644 index 0000000..17da088 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.service.ts @@ -0,0 +1,160 @@ +import { Injectable } from '@angular/core'; +import { environment } from 'src/environments/environment'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable, BehaviorSubject } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ExportService } from 'src/app/shared/export/export.service'; +import { BranchConfig, BranchData } from './branch-config'; + +@Injectable({ + providedIn: 'root' +}) +export class BranchConfigService { + + private branchConfigSubject: BehaviorSubject = new BehaviorSubject(null); + public branchConfig$ = this.branchConfigSubject.asObservable(); + + constructor(private httpClient: HttpClient, private exportService: ExportService) {} + + public buildBranchConfigURL(): string { + return environment.urlBase + '/BranchConfig'; + } + + public buildHeader(): HttpHeaders { + return new HttpHeaders().set('Authorization', 'Bearer ' + localStorage.getItem('adal.idtoken')); + } + + public hasBranchConfig(): boolean { + return this.branchConfigSubject.value !== null; + } + + public loadBranchConfig(): Observable { + const headers = this.buildHeader(); + return this.httpClient.get(this.buildBranchConfigURL(), {headers, withCredentials: true}).pipe( + map((success) => { + const config = new BranchConfig(); + config.id = success['id']; + config.date = success['date']; + config.branchCodesByZip = success['branchCodesByZip']; + return config; + }) + ); + } + + public setBranchConfig(branchConfig: BranchConfig): void { + this.branchConfigSubject.next(branchConfig); + } + + public submitBranchConfig(branchConfig: BranchConfig): Observable { + const headers = this.buildHeader(); + return this.httpClient.put(this.buildBranchConfigURL(), branchConfig, {headers, withCredentials: true}) + .pipe( + map((success) => { + const config = new BranchConfig(); + config.id = success['id']; + config.date = success['date']; + config.branchCodesByZip = success['branchCodesByZip']; + this.setBranchConfig(config); + return config; + }) + ); + } + + public exportCSV(dataSource: Array) { + this.exportService.exportBranchConfigAsCSV(dataSource); + } + + public validateUpload(rawData: string): any { + const pendingData = rawData.split('\n'); + const oldData = this.branchConfigSubject.getValue().branchCodesByZip; + const compareData = { + currentCount: oldData.length, + newCount: pendingData.length - 1, + inError: false, + errorMessage: '', + convertedData: [], + warnings: [] + }; + + if (pendingData.length <= 1) { + compareData.inError = true; + compareData.errorMessage = 'There are no rows.'; + return compareData; + } + + let rowData: BranchData; + let foundRow: BranchData; + try { + for (let newDataIndex = 1; newDataIndex < pendingData.length; newDataIndex++) { + rowData = this.createBranchDataFromRow(pendingData[newDataIndex], newDataIndex); + if (rowData) { + foundRow = this.findExistingRow(oldData, rowData); + if (foundRow === null) { + compareData.warnings.push('Zipcode ' + rowData.zipcode + ' is a newly added zipcode.'); + } else if ((foundRow.channelId + '') !== (rowData.channelId + '')) { + compareData.warnings + .push('Zipcode ' + rowData.zipcode + ' was changed from ' + foundRow.channelId + ' to ' + rowData.channelId + '.'); + } + compareData.convertedData.push(rowData); + } + } + compareData.warnings = compareData.warnings.concat(this.checkForRemovedEntries(oldData, compareData.convertedData)); + compareData.newCount = compareData.convertedData.length; + } catch (error) { + compareData.inError = true; + compareData.errorMessage = error.msg; + } + + return compareData; + } + + public checkForRemovedEntries(oldSet: Array, newSet: Array): Array { + const warnings = []; + let found = false; + for (let oldDataIndex = 0; oldDataIndex < oldSet.length; oldDataIndex++) { + for (let newDataIndex = 0; newDataIndex < newSet.length; newDataIndex++) { + if (oldSet[oldDataIndex].zipcode === newSet[newDataIndex].zipcode) { + found = true; + } + } + if (!found) { + warnings.push('Zipcode ' + oldSet[oldDataIndex].zipcode + ' is not in the new set.'); + } + found = false; + } + return warnings; + } + + public saveNewBranchMapping(pendingData: Array): Observable { + const branchConfig = { + id: this.branchConfigSubject.getValue()['id'], + date: new Date(), + branchCodesByZip: pendingData + }; + return this.submitBranchConfig(branchConfig); + } + + public createBranchDataFromRow(dataString: string, rowIndex: number): BranchData { + if (!dataString || !dataString.trim()) { + return null; + } + const cols = dataString.trim().split(','); + + if (!cols || cols.length !== 2) { + throw new Error('Row ' + rowIndex + ' does not have correct number of columns.'); + } + return { + zipcode: cols[0], + channelId: parseInt(cols[1], 10) + }; + } + + public findExistingRow(oldData: Array, rowData: BranchData): BranchData { + for (let oldDataIndex = 0; oldDataIndex < oldData.length; oldDataIndex++) { + if (oldData[oldDataIndex].zipcode === rowData.zipcode) { + return oldData[oldDataIndex]; + } + } + return null; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.ts new file mode 100644 index 0000000..8ba1e34 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-config/branch-config.ts @@ -0,0 +1,10 @@ +export class BranchData { + zipcode: string; + channelId: number; +} + +export class BranchConfig { + public id: string; + public date: Date; + public branchCodesByZip: Array; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.html new file mode 100644 index 0000000..d0c1c7f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.html @@ -0,0 +1,41 @@ + +
+ +
+

BRANCHNAMEMAP.TITLE

+ BRANCHNAMEMAP.SUBTEXT +

+ BRANCHNAMEMAP.DATETEXT{{lastUpdated | date:'short'}} +

+
+
+
+
+
+
+
BRANCHNAMEMAP.UPDATETEXT
+ +
+
+
+
+
+
+ +
+ + + + + + + + + + + +
Channel ID{{loan.channelId}} Branch Name{{loan.branchName}}
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.scss new file mode 100644 index 0000000..f37a35a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.scss @@ -0,0 +1,38 @@ +table { + width: 100%; +} + +.mat-form-field { + margin-right: 10px; + font-size: .875em; + width: 20%; +} + +.mat-header-cell { + font-size: 1em; + font-weight: bold; + color: black; +} + +.mat-checkbox { + margin-right: 10px; + :host ::ng-deep .mat-checkbox-background { + background-color: #005B9A + } +} + +.btn-disabled { + margin-bottom: 0px; + margin-left: 0px; +} + +.btn-less-padding { + padding-top: 3px; + padding-bottom: 3px; + padding-left: 8px; + padding-right: 8px; +} + +.shift-right { + margin-left: 5px; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.spec.ts new file mode 100644 index 0000000..60475e5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.spec.ts @@ -0,0 +1,66 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { BranchNameConfigComponent } from './branch-name-config.component'; +import { MatSortModule, MatPaginatorModule, MatTableModule, MatInputModule, + MatDatepickerModule, MatNativeDateModule, MatSelectModule, MatCheckboxModule, MatSnackBar, MatSnackBarModule } from '@angular/material'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { LoanCodePipe } from 'src/app/shared/pipes/loan-code.pipe'; +import { BranchNameConfigService } from './branch-name-config.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TopNavigationComponent } from '../navigation/top-navigation.component'; +import { SnackBarErrorComponent } from '../snack-bar-error/snack-bar-error.component'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('BranchNameConfigComponent', () => { + let component: BranchNameConfigComponent; + let fixture: ComponentFixture; + let service: BranchNameConfigService; + let snackBar: MatSnackBar; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + FormsModule, + MatSortModule, + MatPaginatorModule, + MatTableModule, + MatInputModule, + MatSnackBarModule, + MatDatepickerModule, + MatNativeDateModule, + MatSelectModule, + MatCheckboxModule, + ReactiveFormsModule, + HttpClientTestingModule, + NoopAnimationsModule, + TranslateModule.forRoot() + ], + declarations: [ + SnackBarErrorComponent, + BranchNameConfigComponent, + LoanCodePipe, + TopNavigationComponent + ], + providers: [ + ] + }); + }); + + + beforeEach(() => { + service = TestBed.get(BranchNameConfigService); + snackBar = TestBed.get(MatSnackBar); + fixture = TestBed.createComponent(BranchNameConfigComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + expect(service).toBeTruthy(); + fixture.detectChanges(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.ts new file mode 100644 index 0000000..048c31a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { BranchNameConfigService } from './branch-name-config.service'; +import { Subscription } from 'rxjs'; +import { MatTableDataSource, MatSort } from '@angular/material'; +import { BranchNameConfig } from './branch-name-config'; +import { ServerError } from '../snack-bar-error/server-error'; +import { SnackBarErrorComponent } from '../snack-bar-error/snack-bar-error.component'; +import { SnackBarService } from '../snack-bar-error/snack-bar.service'; + +@Component({ + selector: 'lw-branch-Name-config', + templateUrl: './branch-name-config.component.html', + styleUrls: ['./branch-name-config.component.scss'] +}) +export class BranchNameConfigComponent implements OnInit, OnDestroy { + + private dataSubscription: Subscription; + public dataSource: MatTableDataSource; + public lastUpdated: Date; + public displayedColumns: string[] = ['channelId', 'branchName']; + + @ViewChild(MatSort) sort: MatSort; + + constructor(private branchNameConfigService: BranchNameConfigService, private snackBarService: SnackBarService) {} + + public ngOnInit() { + this.subscribeToSourceData(); + if (!this.branchNameConfigService.hasBranchNameConfig()) { + this.loadBranchNameConfig(); + } + } + + public commandUpdate() { + this.loadBranchNameConfig(true); + } + + public loadBranchNameConfig(update: boolean = false): void { + this.branchNameConfigService.loadBranchNameConfig(update).subscribe( + (success: BranchNameConfig) => { + this.branchNameConfigService.setBranchNameConfig(success); + }, + (error) => { + this.snackBarService.throwServerError(error, 'BRANCHNAMEMAP.LOAD_ERROR'); + } + ); + } + + public subscribeToSourceData(): void { + this.dataSubscription = this.branchNameConfigService.branchNameConfig$.subscribe((branchNameConfig) => { + if (branchNameConfig) { + this.dataSource = new MatTableDataSource(branchNameConfig.branchNamesByChannelId); + this.lastUpdated = branchNameConfig.date; + this.dataSource.sortingDataAccessor = (data, sortHeaderId) => data[sortHeaderId].toLocaleLowerCase(); + this.dataSource.sort = this.sort; + } + }); + } + + public ngOnDestroy(): void { + if (this.dataSubscription) { + this.dataSubscription.unsubscribe(); + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.service.spec.ts new file mode 100644 index 0000000..d3b406e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.service.spec.ts @@ -0,0 +1,106 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { configureTestSuite } from 'ng-bullet'; +import { BranchNameConfigService } from './branch-name-config.service'; +import { environment } from 'src/environments/environment'; +import { BranchNameConfig, BranchNameData } from './branch-name-config'; + +describe('BranchNameConfig Service', () => { + + let service: BranchNameConfigService; + let httpTestingController: HttpTestingController; + const urlBase: string = environment.urlBase; + const baseConfig = { + id: '5', + date: new Date(), + branchNamesByChannelId: [ + { + channelId: 9196721, + branchName: 'Watertown' + }, + { + channelId: 9196721, + branchName: 'Brookfield East' + }, + { + channelId: 9196722, + branchName: 'Waukesha' + } + ] + }; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + BranchNameConfigService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(BranchNameConfigService); + httpTestingController = TestBed.get(HttpTestingController); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should build branchName config url', () => { + const url = service.buildBranchNameConfigURL(); + expect(url).toBe(urlBase + '/BranchConfig/BranchNameConfig'); + }); + + it('should build branchName config update url', () => { + const url = service.buildBranchNameConfigUpdateURL(); + expect(url).toBe(urlBase + '/BranchConfig/UpdateBranchNameConfig'); + }); + + it('should identify if there is a branchName config', () => { + const apps = service.hasBranchNameConfig(); + expect(apps).toEqual(false); + }); + + it('should load branchName config', () => { + const testBranchNameConfig = new BranchNameConfig(); + testBranchNameConfig.branchNamesByChannelId = []; + testBranchNameConfig.date = new Date(); + testBranchNameConfig.id = '555'; + + service.loadBranchNameConfig().subscribe((data) => { + expect(data).toEqual(testBranchNameConfig); + service.setBranchNameConfig(data); + } + ); + const req = httpTestingController.expectOne(service.buildBranchNameConfigURL()); + + // Assert that the request is a GET. + expect(req.request.method).toEqual('GET'); + + // Assert that the request contains the appropriate header. + expect(req.request.headers).toEqual(new HttpHeaders().set('Authorization', 'Bearer ' + localStorage.getItem('adal.idtoken'))); + + // Respond with mock data, causing Observable to resolve. + // Subscribe callback asserts that correct data was returned. + req.flush(testBranchNameConfig); + + // Finally, assert that there are no outstanding requests. + httpTestingController.verify(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.service.ts new file mode 100644 index 0000000..e2b7eb0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.service.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@angular/core'; +import { environment } from 'src/environments/environment'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable, BehaviorSubject } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { BranchNameConfig, BranchNameData } from './branch-name-config'; + +@Injectable({ + providedIn: 'root' +}) +export class BranchNameConfigService { + + private branchNameConfigSubject: BehaviorSubject = new BehaviorSubject(null); + public branchNameConfig$ = this.branchNameConfigSubject; + + constructor(private httpClient: HttpClient) {} + + public buildBranchNameConfigURL(): string { + return environment.urlBase + '/BranchConfig/BranchNameConfig'; + } + + public buildBranchNameConfigUpdateURL(): string { + return environment.urlBase + '/BranchConfig/UpdateBranchNameConfig'; + } + + public buildHeader(): HttpHeaders { + return new HttpHeaders().set('Authorization', 'Bearer ' + localStorage.getItem('adal.idtoken')); + } + + public hasBranchNameConfig(): boolean { + return this.branchNameConfigSubject.value !== null; + } + + public loadBranchNameConfig(update: boolean = false): Observable { + const headers = this.buildHeader(); + const url = update ? this.buildBranchNameConfigUpdateURL() : this.buildBranchNameConfigURL(); + return this.httpClient.get(url, {headers, withCredentials: true}).pipe( + map((success) => { + const config = new BranchNameConfig(); + config.id = success['id']; + config.date = success['date']; + config.branchNamesByChannelId = success['branchNamesByChannelId']; + return config; + }) + ); + } + + public setBranchNameConfig(branchNameConfig: BranchNameConfig): void { + this.branchNameConfigSubject.next(branchNameConfig); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.ts new file mode 100644 index 0000000..a534e99 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/branch-name-config/branch-name-config.ts @@ -0,0 +1,10 @@ +export class BranchNameData { + channelId: number; + branchName: string; +} + +export class BranchNameConfig { + public id: string; + public date: Date; + public branchNamesByChannelId: Array; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/card.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/card.component.scss new file mode 100644 index 0000000..0afb131 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/card.component.scss @@ -0,0 +1,71 @@ +.fa-angle-right { + padding-top: 4px; +} + +.main-card-text { + font-weight: bold; + font-size: 25px; +} +.main-card-footer { + margin-bottom: 10px; +} +.card { + font-family: Arial, sans-serif; + margin-bottom: 25px; + text-decoration: none; + color:#565656; +} +.row { + text-align: center; + padding-right: 0px; + padding-left: 0px; +} +.card-body { + padding-right: 0px; + padding-left: 0px; +} +.main-icon { + margin-top: 10px; + margin-bottom: 8px; +} +.footer-text { + margin-right: 8px; +} +.desktop-view { + font-size: 20px; + margin-right: 8px; +} + +/*mobile*/ +@media (max-width: 767px) { + .fa-angle-right { + padding-top: 0px; + } + .main-card-text { + float: left; + font-size: 17px; + } + .main-card-footer { + margin-bottom: 0px; + } + .card { + margin-bottom: 10px; + } + .footer-text { + float: right; + } + .main-icon { + margin-top: 0px; + margin-bottom: 0px; + } + .desktop-view { + display: none; + } + .mobile-view[style] { + display: inline !important; + } + .card-body { + padding-right: 15px; + padding-left: 15px; + } +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.html new file mode 100644 index 0000000..773f73c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.html @@ -0,0 +1,30 @@ + +
+
+
+
+ + + + +
+
+
+
{{cardName}}
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.scss new file mode 100644 index 0000000..afdf01d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.scss @@ -0,0 +1 @@ +// see card-component.scss for styling \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.spec.ts new file mode 100644 index 0000000..5fa89d3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.spec.ts @@ -0,0 +1,45 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { MainCardAdminComponent } from './main-card.component'; + +describe('MainCardComponent', () => { + let component: MainCardAdminComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + MainCardAdminComponent, + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MainCardAdminComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should create with input', () => { + fixture.detectChanges(); + component.desktopIcon = 'fas fa-credit-card fa-3x'; + component.cardName = 'Credit Card'; + component.cardPath = 'credit-card'; + fixture.detectChanges(); + const icon = fixture.nativeElement.querySelectorAll('.fa-credit-card'); + expect(icon).toBeDefined(); + expect(icon.length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.ts new file mode 100644 index 0000000..d700b56 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/cards/main-card/main-card.component.ts @@ -0,0 +1,20 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'lw-main-card', + templateUrl: './main-card.component.html', + styleUrls: ['../card.component.scss'] +}) +export class MainCardAdminComponent { + @Input() + public cardPath: string; + @Input() + public desktopIcon: string; + @Input() + public mobileIcon: string; + @Input() + public cardName: string; + @Input() + public footerName: string; + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-api.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-api.ts new file mode 100644 index 0000000..45fdbf2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-api.ts @@ -0,0 +1,11 @@ +import { FilterField } from './filter-field'; + + +export class FilterApi { + public count: number; + public sortDesc: boolean; + public sortField: string; + public startDate: Date; + public endDate: Date; + public filterField?: FilterField[]; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-field.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-field.ts new file mode 100644 index 0000000..7c8618d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-field.ts @@ -0,0 +1,19 @@ + + +export class FilterField { + public field: string; + public filterValue: string; + public equal: boolean; + public notEqual: boolean; + public greaterThan: boolean; + public lessThan: boolean; + + constructor (field: string, filterValue: string, equal: boolean, notEqual: boolean, greaterThan: boolean, lessThan: boolean) { + this.field = field; + this.filterValue = filterValue; + this.equal = equal; + this.notEqual = notEqual; + this.greaterThan = greaterThan; + this.lessThan = lessThan; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-util.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-util.ts new file mode 100644 index 0000000..b88dd63 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter-util.ts @@ -0,0 +1,90 @@ +import { FilterField } from './filter-field'; +import { FilterApi } from './filter-api'; +import { DateBuilder } from 'src/app/shared/date/date-builder'; +import { Status } from './status'; +import { ApiStatus } from 'src/app/shared/api-models/api-status'; +import { Filter } from './filter'; + + +export class FilterUtil { + + public static getDefaultFilter(): Filter { + const filter: Filter = new Filter(); + filter.count = 1000; + filter.sortDesc = true; + filter.sortField = 'Date'; + filter.startDate = this.buildDefaultStartDate(); + filter.endDate = this.buildDefaultEndDate(); + filter.status = this.getDefaultStatus(); + filter.branch = 'ALL'; + filter.loanType = 'ALL'; + return filter; + } + + public static getDefaultStatus(): Status { + const status: Status = new Status(); + status.error = true; + status.deleted = true; + status.inProgress = true; + status.submitted = false; + status.decisioned = false; + return status; + } + + public static translateFilter(filter: Filter): FilterApi { + const filterApi: FilterApi = new FilterApi(); + filterApi.count = filter.count; + filterApi.startDate = this.buildApiStartDate(filter.startDate); + filterApi.endDate = this.buildApiEndDate(filter.endDate); + filterApi.sortDesc = filter.sortDesc; + filterApi.sortField = filter.sortField; + + const filterField: FilterField [] = [] ; + if (filter.branch !== 'ALL') { + filterField.push(new FilterField('Branch', filter.branch, true, false, false, false)); + } + if (filter.loanType !== 'ALL') { + filterField.push(new FilterField('LoanType', filter.loanType, true, false, false, false)); + } + if (filter.status.error === false) { + filterField.push(new FilterField('Status', ApiStatus.APPLICATION_ERROR, true, true, false, false)); + filterField.push(new FilterField('Status', ApiStatus.VENDOR_ERROR, false, true, false, false)); + } + if (filter.status.deleted === false) { + filterField.push(new FilterField('Status', ApiStatus.DELETED, false, true, false, false)); + } + if (filter.status.inProgress === false) { + filterField.push(new FilterField('Status', ApiStatus.IN_PROGRESS, false, true, false, false)); + } + if (filter.status.submitted === false) { + filterField.push(new FilterField('Status', ApiStatus.SUBMITTED, false, true, false, false)); + } + if (filter.status.decisioned === false) { + filterField.push(new FilterField('Status', ApiStatus.DECISIONED, false, true, false, false)); + } + + filterApi.filterField = filterField; + return filterApi; + } + + public static buildDefaultStartDate(): string { + const startDate = DateBuilder.buildPastDate(7); + return DateBuilder.formatDateAsString(startDate); + } + + public static buildDefaultEndDate(): string { + return DateBuilder.formatDateAsString(new Date()); + } + + public static buildApiStartDate(date: string): Date { + let startDate = DateBuilder.formatStringAsDate(date); + startDate = DateBuilder.setBeginOfDay(startDate); + return startDate; + } + + public static buildApiEndDate(date: string): Date { + let endDate = DateBuilder.formatStringAsDate(date); + endDate = DateBuilder.setEndOfDay(endDate); + return endDate; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter.ts new file mode 100644 index 0000000..18ece76 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/filter.ts @@ -0,0 +1,13 @@ +import { Status } from './status'; + + +export class Filter { + public count: number; + public sortDesc: boolean; + public sortField: string; + public startDate: string; + public endDate: string; + public status: Status; + public branch: string; + public loanType: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/status.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/status.ts new file mode 100644 index 0000000..9126d06 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/filter/status.ts @@ -0,0 +1,9 @@ + + +export class Status { + public error: boolean; + public inProgress: boolean; + public deleted: boolean; + public submitted: boolean; + public decisioned: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.html new file mode 100644 index 0000000..1cc2acd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.html @@ -0,0 +1,24 @@ +
ADMIN.LANDING_TITLE
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.scss new file mode 100644 index 0000000..43ab9b3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.scss @@ -0,0 +1,22 @@ +.loan-header { + font-family: 'Open Sans', sans-serif !important; + font-size:44px; + color:#012E50 +} + +.title-hr { + margin: 0 0 2.5em 0; + color: #B1B1B1; + height: 1px; + border: none; + background-color: #B1B1B1; +} + +/*mobile*/ +@media (max-width: 767px) { + .loan-header { + font-size:30px; + font-weight:400; + text-align: center; + } +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.spec.ts new file mode 100644 index 0000000..40513d0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.spec.ts @@ -0,0 +1,37 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { AdminLandingComponent } from './admin-landing.component'; +import { MainCardAdminComponent } from '../cards/main-card/main-card.component'; + +describe('AdminLandingComponent', () => { + let component: AdminLandingComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + AdminLandingComponent, + MainCardAdminComponent + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminLandingComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.ts new file mode 100644 index 0000000..0d75937 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/landing/admin-landing.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-admin-landing', + templateUrl: './admin-landing.component.html', + styleUrls: ['./admin-landing.component.scss'] +}) +export class AdminLandingComponent { + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.html new file mode 100644 index 0000000..0523d6c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.html @@ -0,0 +1,3 @@ +
{{container | json}}
+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.spec.ts new file mode 100644 index 0000000..bd33ba3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.spec.ts @@ -0,0 +1,39 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { LoanDataComponent } from './loan-data.component'; +import { LoanTableService } from '../loan-table/loan-table.service'; +import { MockLoanTableService } from 'src/app/testing/shared-mocks/loan-table.mock.service'; + +describe('LoanDataComponent', () => { + let component: LoanDataComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + ], + declarations: [ + LoanDataComponent + ], + providers: [ + { provide: LoanTableService, useValue: MockLoanTableService } + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoanDataComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.ts new file mode 100644 index 0000000..9a00a21 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-data/loan-data.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { LoanTableService } from '../loan-table/loan-table.service'; +import { Subscription } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { Location } from '@angular/common'; +import { ApiLoanContainer } from 'src/app/shared/api-models/api-loan-container'; + +@Component({ + selector: 'lw-loan-data', + templateUrl: './loan-data.component.html', + styleUrls: ['./loan-data.component.scss'] +}) +export class LoanDataComponent implements OnInit, OnDestroy { + + public container: ApiLoanContainer; + private subscription: Subscription; + + constructor(private loanTableService: LoanTableService, private route: ActivatedRoute, private location: Location) { } + + public ngOnInit() { + this.subscription = this.route.params.subscribe(params => { + if (params.id) { + this.container = this.loanTableService.getApplicationById(params.id); + } + }); + } + + public ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public commandPrevious() { + this.location.back(); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.html new file mode 100644 index 0000000..df57b20 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.html @@ -0,0 +1,103 @@ + + +
+ + + + + + +
ADMIN.DATE_ERROR
+
+ + +
ADMIN.DATE_ERROR
+
+
+ + + {{loanType.desc}} + + + + + {{branch}} + + + Decisioned + Submitted + In Progress + Error + Deleted + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Id{{loan.id | slice:0:7}} Loan Type{{loan.loanType | loanCode}} + + + + Status + {{loan.status}}Date{{loan.date | date:'short'}}Branch {{loan.branch}} First Name{{loan.firstName}}Last Name{{loan.lastName}}
+ +
+ \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.scss new file mode 100644 index 0000000..4bb254a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.scss @@ -0,0 +1,55 @@ +table { + width: 100%; +} + +.mat-form-field { + margin-right: 10px; + font-size: 14px; + width: 20%; +} + +.mat-header-cell { + font-size: 16px; + color: black; +} + +.mat-checkbox { + margin-right: 10px; + :host ::ng-deep .mat-checkbox-background { + background-color: #005B9A + } +} + +.btn-disabled { + margin-bottom: 0px; + margin-left: 0px; +} + +.btn-less-padding { + padding-top: 3px; + padding-bottom: 3px; + padding-left: 8px; + padding-right: 8px; +} + +.search-tip { + color: #005B9A; + width: 16px; + padding-top: 16px; + margin-right: 4px; +} + +.status-tip { + color: #005B9A; + margin-right: .3em; +} + +::ng-deep .mat-tooltip { + white-space: pre-line !important; +} + +.error { + font-size: 80%; + color: #dc3545; + font-style: italic; +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.spec.ts new file mode 100644 index 0000000..c44e999 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.spec.ts @@ -0,0 +1,58 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { LoanTableService } from '../loan-table/loan-table.service'; +import { MockLoanTableService } from 'src/app/testing/shared-mocks/loan-table.mock.service'; +import { LoanTableComponent } from './loan-table.component'; +import { MatSortModule, MatPaginatorModule, MatTableModule, MatInputModule, + MatDatepickerModule, MatNativeDateModule, MatSelectModule, MatCheckboxModule, MatSnackBar, MatSnackBarModule } from '@angular/material'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { LoanCodePipe } from 'src/app/shared/pipes/loan-code.pipe'; +import { TopNavigationComponent } from '../navigation/top-navigation.component'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('LoanTableComponent', () => { + let component: LoanTableComponent; + let fixture: ComponentFixture; + let service: LoanTableService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + FormsModule, + MatSortModule, + MatPaginatorModule, + MatTableModule, + MatInputModule, + MatDatepickerModule, + MatNativeDateModule, + MatSnackBarModule, + MatSelectModule, + MatCheckboxModule, + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + LoanTableComponent, + LoanCodePipe, + TopNavigationComponent + ], + providers: [ + { provide: LoanTableService, useValue: MockLoanTableService } + ] + }); + }); + + + beforeEach(() => { + service = TestBed.get(LoanTableService); + fixture = TestBed.createComponent(LoanTableComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + expect(service).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.ts new file mode 100644 index 0000000..0546adf --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.component.ts @@ -0,0 +1,195 @@ +import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { LoanTableService } from './loan-table.service'; +import { Subscription } from 'rxjs'; +import { ApiLoanContainer } from 'src/app/shared/api-models/api-loan-container'; +import { MatTableDataSource, MatPaginator, MatSort } from '@angular/material'; +import { Filter } from '../filter/filter'; +import { FormGroup } from '@angular/forms'; +import { FilterUtil } from '../filter/filter-util'; +import { BranchOptions } from 'src/app/shared/options/branch-options'; +import { SelectOptions } from 'src/app/shared/options/select-options'; +import { SnackBarErrorComponent } from '../snack-bar-error/snack-bar-error.component'; +import { ServerError } from '../snack-bar-error/server-error'; +import { SnackBarService } from '../snack-bar-error/snack-bar.service'; +import { BranchNameConfig } from './loan-table'; + +@Component({ + selector: 'lw-loan-table', + templateUrl: './loan-table.component.html', + styleUrls: ['./loan-table.component.scss'] +}) + +export class LoanTableComponent implements OnInit, OnDestroy { + public readonly FORM_STATE_INVALID = 'INVALID'; + public pageForm: FormGroup; + public filterGroup: FormGroup; + public formSubscription: Subscription; + private dataSubscription: Subscription; + public dataSource: MatTableDataSource; + private branchNameConfigDataSubscription: Subscription; + public branchNameConfigDataSource: MatTableDataSource; + public displayedColumns: string[] = ['id', 'loanType', 'status', 'date', 'branch', 'firstName', 'lastName', 'submit', 'delete']; + public filter: Filter; + public lastUpdated: Date; + public branches: string[] = []; + public loanTypes: Array<{value: any, desc: string}>; + public localState = { + filterDisabled: true, + filterIndicator: false, + }; + + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + + constructor(private loanTableService: LoanTableService, private snackBarService: SnackBarService) {} + + public ngOnInit() { + this.filter = this.loanTableService.getFilter(); + this.loanTypes = SelectOptions.loanOptions; + this.subscribeToBranchNameSourceData(); + if (!this.loanTableService.hasBranchNameConfig()) { + this.loadBranchNameConfig(); + } + this.setupForm(); + if (!this.loanTableService.hasApplications()) { + this.loadApplications(); + } + this.subscribeToDataSource(); + } + + public loadBranchNameConfig(): void { + this.loanTableService.loadBranchNameConfig().subscribe( + (success: BranchNameConfig) => { + this.loanTableService.setbranchNameConfig(success); + }, + (error) => { + this.snackBarService.throwServerError(error, 'BRANCHNAMEMAP.LOAD_ERROR'); + } + ); + } + + public subscribeToBranchNameSourceData(): void { + this.dataSubscription = this.loanTableService.branchNameConfig$.subscribe((branchNameConfig) => { + if (branchNameConfig) { + this.branchNameConfigDataSource = new MatTableDataSource(branchNameConfig.branchNamesByChannelId); + this.lastUpdated = branchNameConfig.date; + this.branchNameConfigDataSource.sortingDataAccessor = (data, sortHeaderId) => data[sortHeaderId].toLocaleLowerCase(); + this.branchNameConfigDataSource.sort = this.sort; + var branchList = this.branchNameConfigDataSource.data.map(value => value.branchName); + this.branches = branchList.sort(); + } + }); + } + + public getBranchName(item) { + var branchName = [item.branchName]; + return branchName; + } + + public loadApplications(): void { + this.localState.filterIndicator = true; + this.loanTableService.loadApplications(FilterUtil.translateFilter(this.filter)).subscribe( + (success: ApiLoanContainer[]) => { + this.loanTableService.setApplications(success); + this.localState.filterIndicator = false; + }, + (error) => { + this.localState.filterIndicator = false; + this.snackBarService.throwServerError(error, 'ADMIN.LOAD_ERROR'); + } + ); + } + + public setupForm(): void { + this.pageForm = this.loanTableService.createPageGroup(); + this.filterGroup = this.pageForm.get(this.loanTableService.FILTER_FORM_NAME); + this.formSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.filterDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + this.patchForm(); + } + + public patchForm(): void { + this.filterGroup.patchValue({startDate: this.filter.startDate}); + this.filterGroup.patchValue({endDate: this.filter.endDate}); + this.filterGroup.patchValue({loanType: this.filter.loanType}); + this.filterGroup.patchValue({branch: this.filter.branch}); + this.filterGroup.patchValue({deleted: this.filter.status.deleted}); + this.filterGroup.patchValue({error: this.filter.status.error}); + this.filterGroup.patchValue({inProgress: this.filter.status.inProgress}); + this.filterGroup.patchValue({submitted: this.filter.status.submitted}); + this.filterGroup.patchValue({decisioned: this.filter.status.decisioned}); + } + + public subscribeToDataSource(): void { + this.dataSubscription = this.loanTableService.applicationSet$.subscribe((loanContainers) => { + this.dataSource = new MatTableDataSource(loanContainers); + this.dataSource.paginator = this.paginator; + this.dataSource.sortingDataAccessor = (data, sortHeaderId) => data[sortHeaderId].toLocaleLowerCase(); + this.dataSource.sort = this.sort; + }); + } + + public commandExportCSV(): void { + this.loanTableService.exportCSV(this.dataSource.filteredData); + } + + public commandSearch(searchValue: string): void { + this.dataSource.filter = searchValue.trim().toLowerCase(); + if (this.dataSource.paginator) { + this.dataSource.paginator.firstPage(); + } + } + + public commandFilter(): void { + this.translateFormToFilter(); + this.loanTableService.setFilter(this.filter); + this.loadApplications(); + } + + public commandDelete(loanId: string): void { + this.loanTableService.deleteApplication(loanId).subscribe( + (result) => { + this.loadApplications(); + }, + (error) => { + this.snackBarService.throwServerError(error, 'ADMIN.DELETE_ERROR'); + } + ); + } + + public commandSubmit(loanId: string): void { + this.loanTableService.submitApplication(loanId).subscribe( + (result) => { + this.loadApplications(); + }, + (error) => { + this.snackBarService.throwServerError(error, 'ADMIN.SUBMIT_ERROR'); + } + ); + } + + public translateFormToFilter(): void { + this.filter.startDate = this.filterGroup.value.startDate; + this.filter.endDate = this.filterGroup.value.endDate; + this.filter.loanType = this.filterGroup.value.loanType; + this.filter.branch = this.filterGroup.value.branch; + this.filter.status.deleted = this.filterGroup.value.deleted; + this.filter.status.error = this.filterGroup.value.error; + this.filter.status.inProgress = this.filterGroup.value.inProgress; + this.filter.status.submitted = this.filterGroup.value.submitted; + this.filter.status.decisioned = this.filterGroup.value.decisioned; + } + + public ngOnDestroy(): void { + if (this.formSubscription) { + this.formSubscription.unsubscribe(); + } + if (this.dataSubscription) { + this.dataSubscription.unsubscribe(); + } + if (this.branchNameConfigDataSubscription){ + this.branchNameConfigDataSubscription.unsubscribe(); + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.service.spec.ts new file mode 100644 index 0000000..f72f065 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.service.spec.ts @@ -0,0 +1,131 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { HttpHeaders } from '@angular/common/http'; +import { configureTestSuite } from 'ng-bullet'; +import { LoanTableService } from './loan-table.service'; +import { ApiLoanContainer } from 'src/app/shared/api-models/api-loan-container'; +import { TestApiLoanContainer } from 'src/app/testing/shared-mocks/test-api-loan-container'; +import { environment } from 'src/environments/environment'; +import { FilterUtil } from '../filter/filter-util'; +import { TranslateModule } from '@ngx-translate/core'; + + +describe('LoanTable Service', () => { + + let service: LoanTableService; + const loanContainer: ApiLoanContainer = TestApiLoanContainer; + const loanContainers: ApiLoanContainer[] = [loanContainer]; + let httpTestingController: HttpTestingController; + const urlBase: string = environment.urlBase; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + LoanTableService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(LoanTableService); + httpTestingController = TestBed.get(HttpTestingController); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should build storage url', () => { + const url = service.buildStorageFilterURL(); + expect(url).toBe(urlBase + '/Storage/Filter'); + }); + + it('should build loan application url', () => { + const url = service.buildLoanApplicationURL(); + expect(url).toBe(urlBase + '/LoanApplication/Submit'); + }); + + it('should identify if there are applications', () => { + const apps = service.hasApplications(); + expect(apps).toEqual(false); + }); + + it('should load applications', () => { + service.loadApplications(FilterUtil.translateFilter(FilterUtil.getDefaultFilter())).subscribe((data) => { + expect(data.test).toEqual('x'); + service.setApplications(data); + } + ); + const req = httpTestingController.expectOne(service.buildStorageFilterURL()); + + // Assert that the request is a POST. + expect(req.request.method).toEqual('POST'); + + // Assert that the request contains the appropriate header. + expect(req.request.headers).toEqual(new HttpHeaders().set('Authorization', 'Bearer ' + localStorage.getItem('adal.idtoken'))); + + // Respond with mock data, causing Observable to resolve. + // Subscribe callback asserts that correct data was returned. + req.flush({'test': 'x'}); + + // Finally, assert that there are no outstanding requests. + httpTestingController.verify(); + }); + + it('should delete application', () => { + const loanId = '1234'; + service.deleteApplication(loanId).subscribe(() => {}); + const req = httpTestingController.expectOne(service.buildStorageDeleteURL() + '/' + loanId); + expect(req.request.method).toEqual('DELETE'); + expect(req.request.headers).toEqual(new HttpHeaders().set('Authorization', 'Bearer ' + localStorage.getItem('adal.idtoken'))); + httpTestingController.verify(); + }); + + it('should submit application', () => { + const loanId = '1234'; + service.submitApplication(loanId).subscribe(() => {}); + const req = httpTestingController.expectOne(service.buildLoanApplicationURL() + '/' + loanId); + expect(req.request.method).toEqual('POST'); + httpTestingController.verify(); + }); + + it('should set and get loan containers', () => { + service.setApplications(loanContainers); + let subLoanApps; + service.applicationSet$.subscribe((containers) => { + subLoanApps = containers; + }); + expect(subLoanApps).toEqual(loanContainers); + }); + + it('should get application by id', () => { + service.setApplications(loanContainers); + const loan = service.getApplicationById('12341234'); + expect(loan.branch).toBe('milwaukee'); + }); + + it('should fail with bad application id', () => { + service.setApplications(loanContainers); + expect(function() { service.getApplicationById('0'); }) + .toThrow(new Error('Application not found')); + }); + + it('should fail with no applications loaded', () => { + expect(function() { service.getApplicationById('0'); }) + .toThrow(new Error('Applications are not loaded')); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.service.ts new file mode 100644 index 0000000..445a289 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.service.ts @@ -0,0 +1,135 @@ +import { Injectable } from '@angular/core'; +import { environment } from 'src/environments/environment'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable, BehaviorSubject } from 'rxjs'; +import { ApiLoanContainer } from 'src/app/shared/api-models/api-loan-container'; +import { FilterApi } from '../filter/filter-api'; +import { ExportService } from 'src/app/shared/export/export.service'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { FilterUtil } from '../filter/filter-util'; +import { Filter } from '../filter/filter'; +import { AdminGroups } from '../admin-groups'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { BranchOptions } from 'src/app/shared/options/branch-options'; +import { map } from 'rxjs/operators'; +import { BranchNameConfig, BranchNameData } from './loan-table'; + +@Injectable({ + providedIn: 'root' +}) +export class LoanTableService { + + private applicationSet: BehaviorSubject> = new BehaviorSubject>([]); + public applicationSet$ = this.applicationSet.asObservable(); + private branchNameConfigSubject: BehaviorSubject = new BehaviorSubject(null); + public branchNameConfig$ = this.branchNameConfigSubject.asObservable(); + public readonly FILTER_FORM_NAME = 'filterInformation'; + private filter = FilterUtil.getDefaultFilter(); + + constructor(private formBuilder: FormBuilder, private httpClient: HttpClient, private exportService: ExportService, private configurationService: ConfigurationService) {} + + public buildHeader(): HttpHeaders { + return new HttpHeaders().set('Authorization', 'Bearer ' + localStorage.getItem('adal.idtoken')); + } + + public buildStorageDeleteURL(): string { + return environment.urlBase + '/Storage/Loan'; + } + + public buildStorageFilterURL(): string { + return environment.urlBase + '/Storage/Filter'; + } + + public buildLoanApplicationURL(): string { + return environment.urlBase + '/LoanApplication/Submit'; + } + + public buildBranchNameConfigURL(): string { + return environment.urlBase + '/BranchConfig/BranchNameConfig'; + } + + public loadApplications(filterApi: FilterApi): Observable { + const headers = this.buildHeader(); + return this.httpClient.post(this.buildStorageFilterURL(), filterApi, {headers, withCredentials: true}); + } + + public hasBranchNameConfig(): boolean { + return this.branchNameConfigSubject.value !== null; + } + + public loadBranchNameConfig(): Observable { + const headers = this.buildHeader(); + return this.httpClient.get(this.buildBranchNameConfigURL(), {headers, withCredentials: true}).pipe( + map((success) => { + const config = new BranchNameConfig(); + config.id = success['id']; + config.date = success['date']; + config.branchNamesByChannelId = success['branchNamesByChannelId']; + return config; + }) + ); + } + + public setbranchNameConfig(branchNameConfig: BranchNameConfig): void { + this.branchNameConfigSubject.next(branchNameConfig); + } + + public deleteApplication(loanId: string): Observable { + const headers = this.buildHeader(); + return this.httpClient.delete(this.buildStorageDeleteURL() + '/' + loanId, {headers, withCredentials: true}); + } + + public submitApplication(loanId: string): Observable { + return this.httpClient.post(this.buildLoanApplicationURL() + '/' + loanId, ''); + } + + public hasApplications(): boolean { + return !!(this.applicationSet.value.length); + } + + public setApplications(applications: ApiLoanContainer[]): void { + this.applicationSet.next(applications); + } + + public getApplicationById(loanId: string): ApiLoanContainer { + if (!this.applicationSet.value.length) { + throw new Error('Applications are not loaded'); + } + const single = this.applicationSet.value.find(x => x.id === loanId); + if (!single) { + throw new Error('Application not found'); + } + return single; + } + + public getFilter() { + return this.filter; + } + + public setFilter(filter: Filter) { + this.filter = filter; + } + + public exportCSV(dataSource: ApiLoanContainer[]) { + this.exportService.exportLoanContainerAsCSV(dataSource); + } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + const filterGroup = this.createFilterGroup(); + group.addControl(this.FILTER_FORM_NAME, filterGroup); + return group; + } + + public createFilterGroup(): FormGroup { + const filterGroup = this.formBuilder.group({}); + + AdminGroups.FILTER.fields.forEach((eachField) => { + filterGroup.addControl(eachField.field, this.formBuilder.control('')); + filterGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + return filterGroup; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.ts new file mode 100644 index 0000000..a534e99 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/loan-table/loan-table.ts @@ -0,0 +1,10 @@ +export class BranchNameData { + channelId: number; + branchName: string; +} + +export class BranchNameConfig { + public id: string; + public date: Date; + public branchNamesByChannelId: Array; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.html new file mode 100644 index 0000000..198b041 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.html @@ -0,0 +1,2 @@ + +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.scss new file mode 100644 index 0000000..cb70f71 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.scss @@ -0,0 +1,11 @@ +.btn-home { + margin-bottom: 15px; +} + +.title-hr { + margin: 0 0 15px 0; + color: #B1B1B1; + height: 1px; + border: none; + background-color: #B1B1B1; +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.spec.ts new file mode 100644 index 0000000..7cfb6c3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { TopNavigationComponent } from './top-navigation.component'; + +describe('TopNavigationComponent', () => { + let component: TopNavigationComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + TopNavigationComponent, + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TopNavigationComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.ts new file mode 100644 index 0000000..f94f314 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/navigation/top-navigation.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'lw-top-navigation', + templateUrl: './top-navigation.component.html', + styleUrls: ['./top-navigation.component.scss'] +}) +export class TopNavigationComponent { + constructor(private router: Router) { } + + public commandHome(): void { + this.router.navigate(['/admin']); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.html new file mode 100644 index 0000000..472ec73 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.html @@ -0,0 +1,20 @@ + +
ADMIN.SCHUMER
+
+
+
+
+ Select a loan type to preview the Schumer box: +
+
+ +
+
+
+ + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.scss new file mode 100644 index 0000000..daa3657 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.scss @@ -0,0 +1,11 @@ +$max-width: 770px; + + +@media screen and (max-width: $max-width) { + .url-long { + display: none; + } + .url-short[style] { + display: inline !important; + } +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.ts new file mode 100644 index 0000000..0f35d3a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/schumer-boxes/schumer-preview.component.ts @@ -0,0 +1,18 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'lw-schumer-preview', + templateUrl: './schumer-preview.component.html', + styleUrls: ['./schumer-preview.component.scss'] +}) +export class SchumerPreviewComponent { + + public schumerType = 'CC_CashBack'; + public localState = { + showSchumer: true + }; + + constructor() { + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/server-error.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/server-error.ts new file mode 100644 index 0000000..8cd97c0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/server-error.ts @@ -0,0 +1,11 @@ + + +export class ServerError { + public statusCode: string; + public message: string; + + constructor (statusCode: string, message: string) { + this.statusCode = statusCode; + this.message = message; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.html new file mode 100644 index 0000000..76d926b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.html @@ -0,0 +1,4 @@ + +
HTTP Status: {{error.statusCode}}
+
Message: {{error.message | translate}}
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.ts new file mode 100644 index 0000000..d367be1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar-error.component.ts @@ -0,0 +1,21 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_SNACK_BAR_DATA } from '@angular/material'; +import { ServerError } from './server-error'; + + +@Component({ + selector: 'lw-snack-bar-error', + templateUrl: 'snack-bar-error.component.html', + styles: ['snack-bar-error.component.scss'], +}) + + +export class SnackBarErrorComponent { + public error: ServerError; + + constructor(@Inject(MAT_SNACK_BAR_DATA) public data: ServerError) { + this.error = data; + } +} + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar.service.ts new file mode 100644 index 0000000..4a81497 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/snack-bar-error/snack-bar.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material'; +import { SnackBarErrorComponent } from './snack-bar-error.component'; +import { ServerError } from './server-error'; + +@Injectable({ + providedIn: 'root' +}) +export class SnackBarService { + public errorDurationInSeconds = 10; + + constructor(public snackBar: MatSnackBar) {} + + public throwServerError(error: any, errorMessage: string): void { + this.snackBar.openFromComponent(SnackBarErrorComponent, { + verticalPosition: 'top', + duration: this.errorDurationInSeconds * 1000, + data: new ServerError(this.getStatusFromError(error), errorMessage) + }); + } + + public throwSuccess(message: string): void { + this.snackBar.open(message, 'Okay', { + verticalPosition: 'top', + }); + } + + public getStatusFromError(error: any): string { + if (error && error['status']) { + return error['status']; + } else { + return 'Unknown'; + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.html new file mode 100644 index 0000000..e426dfe --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.html @@ -0,0 +1,25 @@ + +
+
+
+
+

CONFIG.TITLE

+ CONFIG.SUBTEXT +
+
+
+ +
+
+
+ +
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.scss new file mode 100644 index 0000000..8765748 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.scss @@ -0,0 +1,16 @@ +.btn-save { + margin-top: 15px; +} + +.alert-secondary hr { + margin-top: 0px; + margin-bottom: 5px; + border-top-color: #c8cbcf; +} + +.alert-secondary { + background-color: #E2E3E5; + color: #383D41; + border-color: #d6d8db; + margin-bottom: 0px; +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.ts new file mode 100644 index 0000000..6175e0b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.component.ts @@ -0,0 +1,71 @@ +import { Component, OnInit } from '@angular/core'; +import { SystemConfigService } from './system-config.service'; +import { Configuration } from '../../shared/configuration/configuration'; +import { FormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { SnackBarService } from '../snack-bar-error/snack-bar.service'; + +@Component({ + selector: 'lw-system-config', + templateUrl: './system-config.component.html', + styleUrls: ['./system-config.component.scss'] +}) + +export class SystemConfigComponent implements OnInit { + public pageForm: FormGroup; + public configGroup: FormGroup; + public storedSettings: Configuration = new Configuration(); + public formSubscription: Subscription; + public localState = { + saveIndicator: false, + }; + + constructor(private systemConfigService: SystemConfigService, private snackBarService: SnackBarService) {} + + public ngOnInit() { + this.setupForm(); + this.loadStoredSettings(); + } + + public loadStoredSettings() { + this.systemConfigService.getStoredSettings().subscribe ( + (success: Configuration) => { + this.storedSettings = success; + this.patchForm(this.storedSettings); + }, + (error) => { + this.snackBarService.throwServerError(error, 'CONFIG.LOAD_ERROR'); + } + ); + } + + public updateStoredSettings() { + this.localState.saveIndicator = true; + this.systemConfigService.updateStoredSettings(this.storedSettings).subscribe ( + (success: Configuration) => { + this.storedSettings = success; + this.patchForm(this.storedSettings); + this.localState.saveIndicator = false; + this.snackBarService.throwSuccess('Your changes have been successfully updated and published.'); + }, + (error) => { + this.snackBarService.throwServerError(error, 'CONFIG.UPDATE_ERROR'); + this.localState.saveIndicator = false; + } + ); + } + + public commandSaveSettings() { + this.storedSettings.applicationDisabled = this.configGroup.value.appStatus ? false : true; + this.updateStoredSettings(); + } + + public setupForm(): void { + this.pageForm = this.systemConfigService.createPageGroup(); + this.configGroup = this.pageForm.get(this.systemConfigService.CONFIG_FORM_NAME); + } + + public patchForm(config: Configuration): void { + this.configGroup.patchValue({appStatus: config.applicationDisabled ? false : true}); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.service.ts new file mode 100644 index 0000000..522cee1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/admin/system-config/system-config.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { environment } from 'src/environments/environment'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { Configuration } from '../../shared/configuration/configuration'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { AdminGroups } from '../admin-groups'; + +@Injectable({ + providedIn: 'root' +}) +export class SystemConfigService { + + public readonly CONFIG_FORM_NAME = 'configInformation'; + + constructor(private httpClient: HttpClient, private formBuilder: FormBuilder) {} + + public buildHeader(): HttpHeaders { + return new HttpHeaders().set('Authorization', 'Bearer ' + localStorage.getItem('adal.idtoken')); + } + + public buildStoredSettingsURL(): string { + return environment.urlBase + '/UiSettings/StoredSettings'; + } + + public getStoredSettings(): Observable { + const headers = this.buildHeader(); + return this.httpClient.get(this.buildStoredSettingsURL(), {headers, withCredentials: true}); + } + + public updateStoredSettings(settings: Configuration): Observable { + const headers = this.buildHeader(); + return this.httpClient.put(this.buildStoredSettingsURL(), settings, {headers, withCredentials: true}); + } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + const configGroup = this.createConfigGroup(); + group.addControl(this.CONFIG_FORM_NAME, configGroup); + return group; + } + + public createConfigGroup(): FormGroup { + const configGroup = this.formBuilder.group({}); + + AdminGroups.CONFIG.fields.forEach((eachField) => { + configGroup.addControl(eachField.field, this.formBuilder.control('')); + configGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + return configGroup; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/app-routing.module.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/app-routing.module.ts new file mode 100644 index 0000000..312c1c4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/app-routing.module.ts @@ -0,0 +1,125 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { LoanWizardComponent } from './loan-wizard/loan-wizard.component'; +import { SelectLoanComponent } from './select-loan/select-loan.component'; +import { AppUnavailableComponent } from './shared/app-unavailable/app-unavailable-page.component'; +import { DealershipPortalComponent } from './shared/dealership-portal/dealership-portal-page.component'; +import { ConfigurationResolver } from './shared/configuration/configuration.resolver'; +import { ConfigurationService } from './shared/configuration/configuration.service'; +import { TimeoutLandingPageComponent } from './shared/timeout/timeout-landing-page.component'; +import { AppTimeoutGuard } from './shared/guards/app-timeout.guard'; +import { ccRoutes } from './loan-wizard/wizards/credit-card/credit-card-routing.module'; +import { eqRoutes } from './loan-wizard/wizards/equity/equity-routing.module'; +import { veRoutes } from './loan-wizard/wizards/vehicle/vehicle-routing.module'; +import { slRoutes } from './loan-wizard/wizards/signature/signature-routing.module'; +import { SelectCreditCardComponent } from './select-loan/credit-card/select-credit-card.component'; +import { SelectLoanOutletComponent } from './select-loan/outlet/select-loan-outlet.component'; +import { SelectHomeEquityComponent } from './select-loan/home/select-home-equity.component'; +import { SelectVehicleComponent } from './select-loan/vehicle/select-vehicle.component'; +import { SelectSubVehicleComponent } from './select-loan/vehicle/sub-vehicle/select-sub-vehicle.component'; +import { MemberLoginComponent } from './member/member-login/member-login.component'; +import { MemberOutletComponent } from './member/member-outlet/member-outlet.component'; +import { AuthenticationGuard } from 'microsoft-adal-angular6'; +import { AdminRoleGuard } from './shared/guards/admin-role.guard'; +import { environment } from 'src/environments/environment'; +import { TestRoleGuard } from './shared/guards/test-role.guard'; +import { MemberLoginGuard } from './shared/guards/member-login.guard'; +import { MemberVerificationComponent } from './member/member-verification/member-verification.component'; +import { MemberVerificationGuard } from './shared/guards/member-verification-guard'; +import { MemberCoreResolver } from './member/member-core.resolver'; +import { AppDownComponent } from './shared/app-down/app-down-page.component'; + +export const routes: Routes = [ + { + path: 'select-loan', + component: SelectLoanOutletComponent, + resolve: {UiSettings: ConfigurationResolver}, + canActivate: + environment.testGuard ? [AuthenticationGuard, TestRoleGuard, MemberVerificationGuard] : [MemberVerificationGuard], + children: [ + { + path: '', + component: SelectLoanComponent + }, + { + path: 'credit-card', + component: SelectCreditCardComponent + }, + { + path: 'home-equity', + component: SelectHomeEquityComponent, + }, + { + path: 'vehicle', + component: SelectVehicleComponent, + }, + { + path: 'sub-vehicle', + component: SelectSubVehicleComponent, + } + ] + }, + { + path: 'member', + component: MemberOutletComponent, + canActivate: environment.testGuard ? [AuthenticationGuard] : [], + children: [ + { + path: '', + component: MemberLoginComponent + }, + { + path: 'verification', + component: MemberVerificationComponent + }, + ] + }, + { + path: 'admin', + canActivate: [AuthenticationGuard, AdminRoleGuard], + loadChildren: './admin/admin.module#AdminModule' + }, + { + path: 'timeout', + component: TimeoutLandingPageComponent, + canDeactivate: [AppTimeoutGuard] + }, + { + path: 'error', + component: AppUnavailableComponent + }, + { + path: 'dealership-portal', + resolve: {UiSettings: ConfigurationResolver}, + component: DealershipPortalComponent + }, + { + path: 'down', + component: AppDownComponent + }, + { + path: 'loan-wizard', + component: LoanWizardComponent, + resolve: {UiSettings: ConfigurationResolver}, + canActivate: [ MemberLoginGuard ], + canActivateChild: + environment.testGuard ? [AuthenticationGuard, TestRoleGuard, MemberVerificationGuard] : [MemberVerificationGuard], + children: [ + ccRoutes, + eqRoutes, + veRoutes, + slRoutes, + {path: '', redirectTo: '/select-loan', pathMatch: 'prefix'}, + ] + }, + {path: '', redirectTo: '/select-loan', pathMatch: 'full'}, + {path: '**', redirectTo: '/select-loan'}, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, {useHash: false, enableTracing: false})], + exports: [RouterModule], + providers: [ConfigurationService, ConfigurationResolver, MemberCoreResolver] +}) + +export class AppRoutingModule {} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.html new file mode 100644 index 0000000..e770858 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.html @@ -0,0 +1,9 @@ +Skip To Content + + + +
+ +
+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.spec.ts new file mode 100644 index 0000000..5c5bbd9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.spec.ts @@ -0,0 +1,69 @@ +import {TestBed} from '@angular/core/testing'; +import {RouterTestingModule} from '@angular/router/testing'; +import {AppComponent} from './app.component'; +import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; +import {FooterComponent} from './layout/footer/footer.component'; +import {HeaderComponent} from './layout/header/header.component'; +import {TranslateModule} from '@ngx-translate/core'; +import {TimeoutPopupComponent} from './shared/timeout/timeout-popup.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from './shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from './testing/shared-mocks/credit-application-dao.mock.service'; +import { configureTestSuite } from 'ng-bullet'; + + +describe('AppComponent', () => { + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + TranslateModule.forRoot() + ], + declarations: [ + AppComponent, + FooterComponent, + HeaderComponent, + TimeoutPopupComponent + ], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ], + schemas: [ NO_ERRORS_SCHEMA ] + }); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + + it('should render a router outlet', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('router-oulet')).toBeDefined(); + }); + + it('should include a header', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('lw-header')).toBeDefined(); + }); + + it('should include a footer', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('lw-footer')).toBeDefined(); + }); + + it('should include timeout modal', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('lw-timeout-popup')).toBeDefined(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.ts new file mode 100644 index 0000000..cf74a68 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/app.component.ts @@ -0,0 +1,39 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import {TranslateService} from '@ngx-translate/core'; +import { Subscription } from 'rxjs'; +import { NavigationEnd, Router } from '@angular/router'; + +@Component({ + selector: 'lw-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent implements OnInit, OnDestroy { + + private subscriptionRef: Subscription; + public skipLinkPath: string; + constructor(translate: TranslateService, + private router: Router) { + translate.setDefaultLang('en'); + translate.use('en'); + } + + ngOnInit() { + this.subscriptionRef = this.router.events.subscribe((event) => { + if (event instanceof NavigationEnd) { + this.scrollToTop(); + } + this.skipLinkPath = this.router.url.includes('#content') ? `${this.router.url}` : `${this.router.url}#content`; + }); + } + + ngOnDestroy() { + if (this.subscriptionRef) { + this.subscriptionRef.unsubscribe(); + } + } + + private scrollToTop(): void { + document.getElementById('header').scrollIntoView(); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/app.module.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/app.module.ts new file mode 100644 index 0000000..99c825e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/app.module.ts @@ -0,0 +1,281 @@ +import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core'; +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { LoanWizardComponent } from './loan-wizard/loan-wizard.component'; +import { WaypointsComponent } from './layout/waypoints/waypoints.component'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { InputIconComponent } from './loan-wizard/controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from './loan-wizard/controls/error-display/error-display.component'; +import { HttpClientModule, HttpClient } from '@angular/common/http'; +import { SelectLoanComponent } from './select-loan/select-loan.component'; +import { MemberInfoFormComponent } from './loan-wizard/field-groups/member/member-info-form.component'; +import { PersonalInfoFormComponent } from './loan-wizard/field-groups/personal/personal-info-form.component'; +import { AddressInfoFormComponent } from './loan-wizard/field-groups/address/address-info-form.component'; +import { EmploymentInfoFormComponent } from './loan-wizard/field-groups/employment/employment-info-form.component'; +import { AddressInfoPageComponent } from './loan-wizard/wizards/common/address/address-info-page.component'; +import { MemberInfoPageComponent } from './loan-wizard/wizards/common/member/member-info-page.component'; +import { PersonalInfoPageComponent } from './loan-wizard/wizards/common/personal/personal-info-page.component'; +import { EmploymentInfoPageComponent } from './loan-wizard/wizards/common/employment/employment-info-page.component'; +import { FooterComponent } from './layout/footer/footer.component'; +import { HeaderComponent } from './layout/header/header.component'; +import { HeaderIconComponent } from './layout/header/header-icon.component'; +import { PatriotActComponent } from './shared/patriot-act/patriot-act.component'; +import { IsAddressSameInfoFormComponent } from './loan-wizard/field-groups/address/is-address-same-info-form.component'; +import { CreditCardDisclosureFormComponent } from './loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component'; +import { CreditCardDisclosuresPageComponent } from './loan-wizard/wizards/credit-card/credit-card-disclosures-page.component'; +import { CreditCardSchumerBoxComponent } from './loan-wizard/wizards/credit-card/credit-card-schumer-box.component'; +import { SummaryPageComponent } from './loan-wizard/wizards/common/summary/summary-page.component'; +import { TextMaskModule } from 'angular2-text-mask'; +import { PersonalSummaryComponent } from './loan-wizard/wizards/common/summary/personal-summary.component'; +import { ResidenceSummaryComponent } from './loan-wizard/wizards/common/summary/residence-summary.component'; +import { ApplicationSummaryComponent } from './loan-wizard/wizards/common/summary/application-summary.component'; +import { IncomeSummaryComponent } from './loan-wizard/wizards/common/summary/income-summary.component'; +import { EquityMemberInfoPageComponent } from './loan-wizard/wizards/equity/equity-member-info-page.component'; +import { ResidenceInfoFormComponent } from './loan-wizard/field-groups/address/residence-info-form.component'; +import { DecisionPageComponent } from './loan-wizard/wizards/common/decision/decision-page.component'; +import { BeforeYouBeginComponent } from './shared/before-you-begin/before-you-begin.component'; +import { FileUploadComponent } from './shared/file-upload/file-upload.component'; +import { SsnPipe } from './shared/pipes/ssn.pipe'; +import { PhoneTypePipe } from './shared/pipes/phone-type.pipe'; +import { UploadedFilesPipe } from './shared/pipes/uploaded-files.pipe'; +import { PhoneNumberPipe } from './shared/pipes/phone-number.pipe'; +import { StatePipe } from './shared/pipes/state.pipe'; +import { AddressStatusPipe } from './shared/pipes/address-status.pipe'; +import { EmploymentTypePipe } from './shared/pipes/employment-type.pipe'; +import { CollateralInfoFormComponent } from './loan-wizard/field-groups/equity/collateral-info-form.component'; +import { CollateralInfoPageComponent } from './loan-wizard/wizards/equity/collateral-info-page.component'; +import { CollateralSummaryComponent } from './loan-wizard/wizards/common/summary/collateral-summary.component'; +import { BooleanStringPipe } from './shared/pipes/boolean-string.pipe'; +import { OccupancyTypePipe } from './shared/pipes/occupancy-type.pipe'; +import { PropertyTypePipe, MfgHomePropertyTypePipe } from './shared/pipes/property-type.pipe'; +import { GenderInfoFormComponent } from './loan-wizard/field-groups/hmda/gender-info-form.component'; +import { EthnicityInfoFormComponent } from './loan-wizard/field-groups/hmda/ethnicity-info-form.component'; +import { RaceInfoFormComponent } from './loan-wizard/field-groups/hmda/race-info-form.component'; +import { HmdaInfoPageComponent } from './loan-wizard/wizards/common/hmda/hmda-info-page.component'; +import { ElectronicDisclosuresFormComponent } from './loan-wizard/field-groups/disclosures/electronic-disclosures-form.component'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { OtherIncomeInfoFormComponent } from './loan-wizard/field-groups/employment/other-income-info-form.component'; +import { AlimonyInfoFormComponent } from './loan-wizard/field-groups/employment/alimony-info-form.component'; +import { EmploymentFlagFormComponent } from './loan-wizard/field-groups/employment/financial/employment-flag-form.component'; +import { AlimonyFlagFormComponent } from './loan-wizard/field-groups/employment/financial/alimony-flag-form.component'; +import { OtherIncomeFlagFormComponent } from './loan-wizard/field-groups/employment/financial/other-income-flag-form.component'; +import { VehicleCollateralInfoPageComponent } from './loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component'; +import { VehicleCollateralInfoFormComponent } from './loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component'; +import { AppUnavailableComponent } from './shared/app-unavailable/app-unavailable-page.component'; +import { DealershipPortalComponent } from './shared/dealership-portal/dealership-portal-page.component'; +import { TimeoutPopupComponent } from './shared/timeout/timeout-popup.component'; +import { TimeoutLandingPageComponent } from './shared/timeout/timeout-landing-page.component'; +import { TattleSpousePersonalInfoFormComponent } from './loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component'; +import { TattleSpouseInfoPageComponent } from './loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component'; +import { SpouseTypeInfoPageComponent } from './loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component'; +import { IsFullInfoSpouseInfoFormComponent } from './loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component'; +import { IsRentedLotInfoFormComponent } from './loan-wizard/field-groups/equity/is-rented-lot-info-form.component'; +import { TattleSpouseSummaryComponent } from './loan-wizard/wizards/common/summary/tattle-spouse-summary.component'; +import { environment } from '../environments/environment'; +import { DecisionAnimationComponent } from './layout/decision-animation/decision-animation.component'; +import { JointCreditFormComponent } from './loan-wizard/field-groups/warning-modals/joint-credit-confirmation.component'; +import { ProcessingPageComponent } from './loan-wizard/wizards/common/processing/processing-page.component'; +import { DecisionHeroComponent } from './layout/decision-hero/decision-hero.component'; +import { AutoFocusDirective } from './shared/page-focus/auto-focus.directive'; +import { VehicleSummaryComponent } from './loan-wizard/wizards/common/summary/vehicle-summary.component'; +import { VehicleSellerPipe } from './shared/pipes/vehicle-collateral.pipe'; +import { DisclosureBoxesComponent } from './shared/legal-message/disclosure-boxes.component'; +import { NoteFormComponent } from './loan-wizard/field-groups/summary/note-form.component'; +import { AdditionalInfoFormComponent } from './loan-wizard/field-groups/summary/additional-info-form.component'; +import { BUILD_STAMP } from '../environments/version'; +import { AddressCompareInfoFormComponent } from './loan-wizard/field-groups/address-compare/address-compare-info-form.component'; +import { VehicleTypePipe } from './shared/pipes/vehicle-type.pipe'; +import { AuthenticationGuard, MsAdalAngular6Module } from 'microsoft-adal-angular6'; +import { AppLoaderComponent } from './shared/app-loader/app-loader.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { TcpaComponent } from './shared/legal-message/tcpa.component'; +import { SelectCreditCardComponent } from './select-loan/credit-card/select-credit-card.component'; +import { SelectHomeEquityComponent } from './select-loan/home/select-home-equity.component'; +import { SelectVehicleComponent } from './select-loan/vehicle/select-vehicle.component'; +import { MainCardComponent } from './select-loan/cards/main-card/main-card.component'; +import { SubCardComponent } from './select-loan/cards/sub-card/sub-card.component'; +import { SelectLoanOutletComponent } from './select-loan/outlet/select-loan-outlet.component'; +import { SelectSubVehicleComponent } from './select-loan/vehicle/sub-vehicle/select-sub-vehicle.component'; +import { MemberLoginComponent } from './member/member-login/member-login.component'; +import { MemberOutletComponent } from './member/member-outlet/member-outlet.component'; +import { SharedModule } from './shared/shared.module'; +import { MemberVerificationComponent } from './member/member-verification/member-verification.component'; +import { LoanIconPipe } from './shared/pipes/loan-icon.pipe'; +import { HttpErrorReporter } from './shared/http-error-reporter.service'; +import { AppDownComponent } from './shared/app-down/app-down-page.component'; +function resolveRedirectUri(): string { + if (typeof window === 'undefined') { + return environment.msAuthRedirectUri; + } + + const rawRedirectUri = environment.msAuthRedirectUri; + if (rawRedirectUri) { + try { + const parsed = new URL(rawRedirectUri, window.location.origin); + return `${window.location.origin}${parsed.pathname}${parsed.search}${parsed.hash}`; + } catch (error) { + // Fall back to origin if the configured redirect URI is invalid. + } + } + + const msAuthRedirectUri = window.location.origin; + + return window.location.pathname === '/admin' ? window.location.href : msAuthRedirectUri; +} + +export function adalConfigFactory() { + return { + tenant: '63cd1554-2783-4a86-ae50-b9e6c21c9499', + clientId: '59d54f39-3ff9-4166-8b16-254c00416754', + redirectUri: resolveRedirectUri(), + endpoints: {}, + navigateToLoginRequestUrl: false, + cacheLocation: 'localStorage' + }; +} + +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, environment.baseHref + '/assets/i18n/', '.json?cb=' + new Date().getTime()); +} + +@NgModule({ + declarations: [ + AddressCompareInfoFormComponent, + AddressInfoFormComponent, + AddressInfoPageComponent, + AddressStatusPipe, + AlimonyFlagFormComponent, + AlimonyInfoFormComponent, + AppComponent, + ApplicationSummaryComponent, + AppLoaderComponent, + AppDownComponent, + AppUnavailableComponent, + DealershipPortalComponent, + AutoFocusDirective, + BeforeYouBeginComponent, + FileUploadComponent, + BooleanStringPipe, + CollateralInfoFormComponent, + CollateralInfoPageComponent, + CollateralSummaryComponent, + CreditCardDisclosureFormComponent, + CreditCardDisclosuresPageComponent, + CreditCardSchumerBoxComponent, + DecisionAnimationComponent, + DecisionPageComponent, + DecisionHeroComponent, + DisclosureBoxesComponent, + ElectronicDisclosuresFormComponent, + EmploymentFlagFormComponent, + EmploymentInfoFormComponent, + EmploymentInfoPageComponent, + EmploymentTypePipe, + EthnicityInfoFormComponent, + EquityMemberInfoPageComponent, + ErrorDisplayComponent, + FooterComponent, + GenderInfoFormComponent, + HeaderComponent, + HeaderIconComponent, + HmdaInfoPageComponent, + IncomeSummaryComponent, + InputIconComponent, + IsAddressSameInfoFormComponent, + IsFullInfoSpouseInfoFormComponent, + IsRentedLotInfoFormComponent, + JointCreditFormComponent, + TcpaComponent, + LoanWizardComponent, + LoanIconPipe, + MainCardComponent, + MemberLoginComponent, + MemberVerificationComponent, + MemberOutletComponent, + MemberInfoFormComponent, + MemberInfoPageComponent, + MfgHomePropertyTypePipe, + NoteFormComponent, + AdditionalInfoFormComponent, + OccupancyTypePipe, + OtherIncomeFlagFormComponent, + OtherIncomeInfoFormComponent, + PatriotActComponent, + PersonalInfoFormComponent, + PersonalInfoPageComponent, + PersonalSummaryComponent, + PhoneNumberPipe, + PhoneTypePipe, + UploadedFilesPipe, + ProcessingPageComponent, + PropertyTypePipe, + RaceInfoFormComponent, + ResidenceSummaryComponent, + ResidenceInfoFormComponent, + SelectLoanOutletComponent, + SelectLoanComponent, + SelectCreditCardComponent, + SelectHomeEquityComponent, + SelectVehicleComponent, + SelectSubVehicleComponent, + SpouseTypeInfoPageComponent, + SsnPipe, + StatePipe, + SubCardComponent, + SummaryPageComponent, + TattleSpousePersonalInfoFormComponent, + TattleSpouseInfoPageComponent, + TattleSpouseSummaryComponent, + TimeoutPopupComponent, + TimeoutLandingPageComponent, + VehicleCollateralInfoPageComponent, + VehicleCollateralInfoFormComponent, + VehicleSellerPipe, + VehicleTypePipe, + VehicleSummaryComponent, + WaypointsComponent + ], + imports: [ + AppRoutingModule, + BrowserAnimationsModule, + FormsModule, + HttpClientModule, + NgbModule.forRoot(), + SharedModule, + TextMaskModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: (createTranslateLoader), + deps: [HttpClient] + } + }), + ReactiveFormsModule, + MsAdalAngular6Module + ], + providers: [ + { + provide: 'adalConfig', + useFactory: adalConfigFactory, + }, + AuthenticationGuard, + {provide: ErrorHandler, useClass: HttpErrorReporter} + ], + bootstrap: [AppComponent], + entryComponents: [ + AddressCompareInfoFormComponent, + AddressInfoFormComponent, + CollateralInfoFormComponent, + IsRentedLotInfoFormComponent, + ResidenceInfoFormComponent, + EmploymentInfoFormComponent, + IsAddressSameInfoFormComponent, + IsFullInfoSpouseInfoFormComponent, + MemberInfoFormComponent, + TattleSpousePersonalInfoFormComponent, + TimeoutPopupComponent + ] +}) +export class AppModule {} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.html new file mode 100644 index 0000000..7a831ae --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.spec.ts new file mode 100644 index 0000000..90d9640 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.spec.ts @@ -0,0 +1,67 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { DecisionAnimationComponent } from './decision-animation.component'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ProcessingStep } from '../../loan-wizard/wizards/common/processing/processing-step'; + +describe('Decision Animation Component', () => { + let component: DecisionAnimationComponent; + let fixture: ComponentFixture; + const preMsg = 'test_pre_msg'; + const postMsg = 'test_post_msg'; + const hasStatus = false; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ NgbModule.forRoot()], + declarations: [ DecisionAnimationComponent ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DecisionAnimationComponent); + component = fixture.componentInstance; + component.step = new ProcessingStep( preMsg, postMsg, hasStatus); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create with correct values', () => { + expect(component.step).toBeTruthy(); + expect(component.step.preMessage).toEqual(preMsg); + expect(component.step.postMessage).toEqual(postMsg); + expect(component.step.hasStatus).toEqual(hasStatus); + }); + + it('should set current', () => { + expect(component.step).toBeTruthy(); + component.step.setCurrent(true); + expect(component.step.isActive).toEqual(true); + }); + + it('should set complete', () => { + component.step.setComplete(true); + expect(component.step.isActive).toEqual(false); + expect(component.step.isComplete).toEqual(true); + }); + + it('should get message', () => { + component.step.setComplete(false); + let message = component.step.getMessage(); + expect(message).toEqual(preMsg); + component.step.setComplete(true); + message = component.step.getMessage(); + expect(message).toEqual(postMsg); + }); + + it('should set post message', () => { + const message = 'a different test message'; + expect(component.step.postMessage).toEqual(postMsg); + component.step.setPostMessage(message); + expect(component.step.postMessage).toEqual(message); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.ts new file mode 100644 index 0000000..6108ed1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-animation/decision-animation.component.ts @@ -0,0 +1,15 @@ +import { Component, Input } from '@angular/core'; +import { ProcessingStep } from '../../loan-wizard/wizards/common/processing/processing-step'; + +@Component({ + selector: 'lw-decision-animation', + templateUrl: './decision-animation.component.html', + styleUrls: ['./decision-animation.component.scss'] +}) +export class DecisionAnimationComponent { + + @Input() + step: ProcessingStep; + + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.html new file mode 100644 index 0000000..682e468 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.html @@ -0,0 +1,5 @@ +
+

+

+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.spec.ts new file mode 100644 index 0000000..204d1af --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.spec.ts @@ -0,0 +1,73 @@ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; + +import {DecisionHeroComponent, HeroType} from './decision-hero.component'; +import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; +import {TranslateModule, TranslateService} from '@ngx-translate/core'; +import {of} from 'rxjs'; +import {configureTestSuite} from 'ng-bullet'; +import {LoanTypeSet} from '../../shared/loan-type'; + +describe('Decision Hero Component', () => { + let component: DecisionHeroComponent; + let fixture: ComponentFixture; + let translateService: TranslateService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [NgbModule.forRoot(), TranslateModule.forRoot()], + declarations: [DecisionHeroComponent] + }); + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [NgbModule.forRoot(), TranslateModule.forRoot()], + declarations: [DecisionHeroComponent] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DecisionHeroComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + translateService = TestBed.get(TranslateService); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + component.displayName = 'John'; + component.heroType = HeroType.SUCCESS; + fixture.detectChanges(); + }); + + it('should find the right key for a type', () => { + expect(component).toBeTruthy(); + expect(component.lookupKeyForType(null)).toEqual('DECISION.ERROR_SUBMIT'); + expect(component.lookupKeyForType(HeroType.REVIEW)).toEqual('DECISION.REVIEW'); + expect(component.lookupKeyForType(HeroType.ERROR_SUBMIT)).toEqual('DECISION.ERROR_SUBMIT'); + expect(component.lookupKeyForType(HeroType.ERROR_DECISION)).toEqual('DECISION.ERROR_DECISION'); + }); + + it('should ask the translate service for the messaging', () => { + expect(component).toBeTruthy(); + spyOn(component, 'lookupKeyForType').and.returnValue('DECISION.ERROR_SUBMIT'); + spyOn(translateService, 'get').and.returnValue(of('AAA')); + component.ngOnChanges(null); + expect(translateService.get).toHaveBeenCalledTimes(2); + expect(component.lookupKeyForType).toHaveBeenCalled(); + }); + + it('should ask the translate service for the messaging', () => { + expect(component).toBeTruthy(); + expect(LoanTypeSet[0].decisionReviewKey).toEqual('TEXT_CC'); + component.decisionKey = LoanTypeSet[0].decisionReviewKey; + spyOn(component, 'lookupKeyForType').and.returnValue('DECISION.REVIEW'); + spyOn(translateService, 'get').and.returnValue(of('AAA')); + component.ngOnChanges(null); + expect(translateService.get).toHaveBeenCalledWith('DECISION.REVIEW.TITLE', {displayName: undefined}); + expect(translateService.get).toHaveBeenCalledWith('DECISION.REVIEW.TEXT_CC', {displayName: undefined}); + expect(translateService.get).toHaveBeenCalledTimes(2); + expect(component.lookupKeyForType).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.ts new file mode 100644 index 0000000..ef534a9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/decision-hero/decision-hero.component.ts @@ -0,0 +1,62 @@ +import {Component, Input, OnChanges, SimpleChanges} from '@angular/core'; +import {TranslateService} from '@ngx-translate/core'; + +export enum HeroType { + SUCCESS, + REVIEW, + DENIED, + ERROR_SUBMIT, + ERROR_DECISION +} + +@Component({ + selector: 'lw-decision-hero', + templateUrl: './decision-hero.component.html', + styleUrls: ['./decision-hero.component.scss'] +}) +export class DecisionHeroComponent implements OnChanges { + + public localState = { + text: '', + title: '' + }; + + @Input() + public decisionKey: string; + + @Input() + public heroType: HeroType; + + @Input() + public displayName: string; + + constructor(private translateService: TranslateService) { + } + + public ngOnChanges(changes: SimpleChanges): void { + const key = this.lookupKeyForType(this.heroType); + const decisionKey = this.decisionKey || 'TEXT'; + this.translateService.get(key + '.TITLE', {displayName: this.displayName}).subscribe((res: string) => { + this.localState.title = res; + }); + this.translateService.get(key + '.' + decisionKey, {displayName: this.displayName}).subscribe((res: string) => { + this.localState.text = res; + }); + } + + public lookupKeyForType(heroType: HeroType) { + switch (heroType) { + case HeroType.SUCCESS: + return 'DECISION.APPROVED'; + case HeroType.REVIEW: + return 'DECISION.REVIEW'; + case HeroType.DENIED: + return 'DECISION.DENIED'; + case HeroType.ERROR_DECISION: + return 'DECISION.ERROR_DECISION'; + default: + case HeroType.ERROR_SUBMIT: + return 'DECISION.ERROR_SUBMIT'; + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.html new file mode 100644 index 0000000..f14003d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.html @@ -0,0 +1,27 @@ +
+
+
+ +
+
+ FOOTER.NCUA + +
+ +
+ FOOTER.HOUSING + +
+ +
+ FOOTER.SECURE +
+
+
+ FOOTER.COPY_RIGHT +
+ +
+
+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.spec.ts new file mode 100644 index 0000000..3caa61b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.spec.ts @@ -0,0 +1,29 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FooterComponent } from './footer.component'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { configureTestSuite } from 'ng-bullet'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +describe('Footer Component', () => { + let component: FooterComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ NgbModule.forRoot()], + declarations: [ FooterComponent ], + schemas: [ NO_ERRORS_SCHEMA ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(FooterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.ts new file mode 100644 index 0000000..e46e600 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/footer/footer.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { BUILD_STAMP, VERSION } from '../../../environments/version'; + +@Component({ + selector: 'lw-footer', + templateUrl: './footer.component.html', + styleUrls: ['./footer.component.scss'] +}) +export class FooterComponent { + + public readonly routingNumber = '275079714'; + public versionHint = VERSION.version + '-' + BUILD_STAMP; + public currentYear = new Date().getFullYear(); + + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.html new file mode 100644 index 0000000..0e6ba98 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.spec.ts new file mode 100644 index 0000000..9e6f622 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.spec.ts @@ -0,0 +1,27 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { HeaderIconComponent } from './header-icon.component'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Header Icon Component', () => { + let component: HeaderIconComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ NgbModule.forRoot()], + declarations: [ HeaderIconComponent ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HeaderIconComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.ts new file mode 100644 index 0000000..d105a6c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header-icon.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'lw-header-icon', + templateUrl: './header-icon.component.html', + styleUrls: ['./header.component.scss'] +}) +export class HeaderIconComponent { + @Input() + public applicationIcon: string; + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.html new file mode 100644 index 0000000..8af0bfe --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.html @@ -0,0 +1,34 @@ +
+
+ + +
+
+
+
+ + {{applicantType?.headerMessageKey | translate}} +
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.scss new file mode 100644 index 0000000..54981b3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.scss @@ -0,0 +1,53 @@ +$primary: #005674; +$primaryDark: #053251; +$max-width: 770px; + +.navbar-lmcu { + border-bottom: 3px solid #b3c9e4; +} + +.logo-lmcu { + height: 70px; +} + +.phone-button { + display: none; +} + +.header-information-small { + display: none !important; +} + +@media screen and (max-width: $max-width) { + .logo-lmcu { + height: 35px; + } + .phone-button { + position: absolute; + display: inline; + border: $primary; + right: -15px; + top:0; + background-color: $primary; + height: 60px; + width: 60px; + cursor:pointer; + color: white; + } + .phone-button-icon { + width: 1.5rem; + height: 1.5rem; + } + .phone-button:hover { + background-color: $primaryDark; + color:gray; + } + + .header-information-big { + display: none; + } + .header-information-small { + display: inline !important; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.spec.ts new file mode 100644 index 0000000..063cacb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.spec.ts @@ -0,0 +1,121 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HeaderComponent } from './header.component'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; +import { HeaderService } from './header.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TextMaskModule } from 'angular2-text-mask'; +import { CreditApplicationDaoService } from '../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../testing/shared-mocks/credit-application-dao.mock.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ApplicationWizardService } from '../../loan-wizard/application-wizard.service'; +import { LoanTypeSet } from '../../shared/loan-type'; +import { HeaderIconComponent } from './header-icon.component'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Header Component', () => { + let component: HeaderComponent; + let fixture: ComponentFixture; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let service: HeaderService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot()], + declarations: [ HeaderComponent, HeaderIconComponent], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ], + schemas: [ NO_ERRORS_SCHEMA ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HeaderComponent); + component = fixture.componentInstance; + service = TestBed.get(HeaderService); + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should update icons/text based on loan type ( credit card ) ', () => { + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + // set to credit card + applicationWizardService.setLoanType(LoanTypeSet[0].id); + fixture.detectChanges(); + const creditCard = compiled.querySelectorAll('.fa-credit-card'); + const header = compiled.querySelectorAll('.header-information-big .col-6 small'); + const home = compiled.querySelectorAll('.fa-home'); + const vehicle = compiled.querySelectorAll('.fa-car'); + + expect(creditCard).toBeDefined(); + expect(home).toBeDefined(); + expect(vehicle).toBeDefined(); + expect(header).toBeDefined(); + + expect(creditCard.length).toBeGreaterThanOrEqual(1); + expect(home.length).toEqual(0); + expect(vehicle.length).toEqual(0); + expect(header[0].innerHTML).toContain(LoanTypeSet[0].headerMessageKey); + }); + + it('should update icons/text based on loan type ( equity ) ', () => { + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + // set to equity + applicationWizardService.setLoanType(LoanTypeSet[4].id); + fixture.detectChanges(); + const creditCard = compiled.querySelectorAll('.fa-credit-card'); + const header = compiled.querySelectorAll('.header-information-big .col-6 small'); + const home = compiled.querySelectorAll('.fa-home'); + const vehicle = compiled.querySelectorAll('.fa-car'); + + expect(creditCard).toBeDefined(); + expect(home).toBeDefined(); + expect(vehicle).toBeDefined(); + expect(header).toBeDefined(); + + expect(creditCard).toBeDefined(); + expect(home).toBeDefined(); + expect(vehicle).toBeDefined(); + expect(creditCard.length).toEqual(0); + expect(home.length).toBeGreaterThanOrEqual(1); + expect(vehicle.length).toEqual(0); + expect(header[0].innerHTML).toContain(LoanTypeSet[4].headerMessageKey); + }); + + it('should update icons/text based on loan type ( vehicle ) ', () => { + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + // set to vehicle + applicationWizardService.setLoanType(LoanTypeSet[7].id); + fixture.detectChanges(); + const creditCard = compiled.querySelectorAll('.fa-credit-card'); + const header = compiled.querySelectorAll('.header-information-big .col-6 small'); + const home = compiled.querySelectorAll('.fa-home'); + const vehicle = compiled.querySelectorAll('.fa-car'); + + expect(creditCard).toBeDefined(); + expect(home).toBeDefined(); + expect(vehicle).toBeDefined(); + expect(header).toBeDefined(); + + expect(creditCard.length).toEqual(0); + expect(home.length).toEqual(0); + expect(vehicle.length).toBeGreaterThanOrEqual(1); + expect(header[0].innerHTML).toContain(LoanTypeSet[7].headerMessageKey); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.ts new file mode 100644 index 0000000..b51d9bf --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.component.ts @@ -0,0 +1,29 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { HeaderService } from './header.service'; +import { Subscription } from 'rxjs'; +import { LoanType } from '../../shared/loan-type'; + +@Component({ + selector: 'lw-header', + templateUrl: './header.component.html', + styleUrls: ['./header.component.scss'] +}) +export class HeaderComponent implements OnInit, OnDestroy { + + public applicantType: LoanType; + private subscriptionRef: Subscription; + + constructor(private headerService: HeaderService) { } + + ngOnInit() { + this.subscriptionRef = this.headerService.getApplicantTypeObservable().subscribe(value => { + this.applicantType = value; + }); + } + + ngOnDestroy() { + if (this.subscriptionRef) { + this.subscriptionRef.unsubscribe(); + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.service.spec.ts new file mode 100644 index 0000000..ec743b3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.service.spec.ts @@ -0,0 +1,61 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +import { HeaderService } from './header.service'; +import { ApplicationWizardService } from '../../loan-wizard/application-wizard.service'; +import { MockCreditApplicationDaoService } from '../../testing/shared-mocks/credit-application-dao.mock.service'; +import { Router } from '@angular/router'; +import { CreditApplicationDaoService } from '../../shared/credit-application-dao.service'; +import { LoanTypeSet } from '../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Header Service', () => { + + let service: HeaderService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ], + schemas: [ + NO_ERRORS_SCHEMA + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(HeaderService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + router = TestBed.get(Router); + }); + + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should get applicant observable', () => { + spyOn(service, 'getApplicantTypeObservable').and.callThrough(); + const observable$ = service.getApplicantTypeObservable(); + expect(service.getApplicantTypeObservable).toHaveBeenCalled(); + expect(observable$).toBeDefined(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.service.ts new file mode 100644 index 0000000..588ff78 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/header/header.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { LoanType } from '../../shared/loan-type'; +import { CreditApplicationService } from '../../shared/credit-application.service'; + + +@Injectable({ + providedIn: 'root' +}) +export class HeaderService { + + constructor(private creditApplicationService: CreditApplicationService) { + } + + public getApplicantTypeObservable(): Observable { + return this.creditApplicationService.getLoanTypeObservable(); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.service.spec.ts new file mode 100644 index 0000000..c50ed35 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.service.spec.ts @@ -0,0 +1,106 @@ +import { Waypoints, WaypointService } from './waypoint.service'; +import { Waypoint } from './waypoint'; + +const stepIndex = 4; // start counting at 0 +const amountOfSteps = 5; // number of steps in the stepper + +describe('Waypoint Service', () => { + let service: WaypointService; + + beforeEach(() => { + service = new WaypointService(); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should have an initial value', () => { + let testData = null; + service.waypointSet$.subscribe((data) => testData = data); + expect(testData).not.toBeNull(); + expect(testData.length).toEqual(amountOfSteps); + const waypoint = testData[0]; + expect(waypoint.id).toEqual(0); + expect(waypoint.isActive).toEqual(false); + }); + + it('should send out updated values on change', () => { + let testData = null; + service.waypointSet$.subscribe((data) => testData = data); + expect(testData).not.toBeNull(); + testData = null; + service.setStepActive(2); + expect( testData ).not.toBeNull(); + }); + + it('should only set a single step as active.', () => { + let testData = null; + service.waypointSet$.subscribe((data) => testData = data); + expect(testData).not.toBeNull(); + expect( testData[0].isActive).toEqual(false); + expect( testData[1].isActive).toEqual(false); + service.setStepActive(0); + expect( testData[0].isActive).toEqual(true); + expect( testData[1].isActive).toEqual(false); + service.setStepActive(1); + expect( testData[0].isActive).toEqual(false); + expect( testData[1].isActive).toEqual(true); + }); + + it('should throw an error on invalid index.', () => { + expect( () => { service.setStepActive(-1); }).toThrowError(); + expect( () => { service.setStepActive(stepIndex); }).not.toThrowError(); + expect( () => { service.setStepActive(stepIndex + 1); }).toThrowError(); + expect( () => { service.setStepActive(99); }).toThrowError(); + }); + + it('should any steps as done.', () => { + let testData = null; + service.waypointSet$.subscribe((data) => testData = data); + expect(testData).not.toBeNull(); + expect( testData[Waypoints.APPLICANT].isComplete).toEqual(false); + expect( testData[Waypoints.REVIEW].isComplete).toEqual(false); + service.setStepActive(Waypoints.REVIEW); + expect( testData[Waypoints.APPLICANT].isComplete).toEqual(true); + expect( testData[Waypoints.REVIEW].isComplete).toEqual(false); + service.setStepActive(Waypoints.FINISH); + expect( testData[Waypoints.APPLICANT].isComplete).toEqual(true); + expect( testData[Waypoints.REVIEW].isComplete).toEqual(true); + }); + + it('should show collateral if it is required.', () => { + let testData = null; + service.waypointSet$.subscribe((data) => testData = data); + expect(testData).not.toBeNull(); + service.showCollateral(true); + expect( testData[Waypoints.COLLATERAL].shouldBeShown).toEqual(true); + service.showCollateral(false); + expect( testData[Waypoints.COLLATERAL].shouldBeShown).toEqual(false); + }); + + it('should set steps as complete up to the active step.', () => { + spyOn(service, 'setStepActive').and.callThrough(); + let testData = null; + service.waypointSet$.subscribe((data) => testData = data); + expect(testData).not.toBeNull(); + service.setStepActive(Waypoints.REVIEW); + expect(service.setStepActive).toHaveBeenCalled(); + expect(testData[Waypoints.REVIEW].isActive).toBe(true); + expect(testData[Waypoints.APPLICANT].isComplete).toBe(true); + expect(testData[Waypoints.FINISH].isComplete).toBe(false); + }); + + it('should set all steps as complete when finished.', () => { + spyOn(service, 'setAllComplete').and.callThrough(); + let testData = null; + service.waypointSet$.subscribe((data) => testData = data); + expect(testData).not.toBeNull(); + service.setAllComplete(true); + expect(service.setAllComplete).toHaveBeenCalled(); + testData.forEach((waypoint) => { + expect(waypoint.isComplete).toBe(true); + }); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.service.ts new file mode 100644 index 0000000..868bd42 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.service.ts @@ -0,0 +1,162 @@ +import { Waypoint } from './waypoint'; +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { environment } from 'src/environments/environment'; + +export enum Waypoints { + APPLICANT, + CO_APPLICANT, + COLLATERAL, + REVIEW, + FINISH +} + +@Injectable({ + providedIn: 'root' +}) +export class WaypointService { + + private readonly applicantWayPoint = { + displayText: 'Applicant', + activeImagePath: `${environment.baseHref}/assets/waypoint/applicant-icon-blue-01.png`, + inactiveImagePath: `${environment.baseHref}/assets/waypoint/applicant-icon-gray-01.png`, + id: 0, + isActive: false, + isComplete: false, + shouldBeShown: true, + comment: null, + displayNumber: 1 + }; + + private readonly coapplicantWayPoint = { + displayText: 'Co-Applicant', + activeImagePath: `${environment.baseHref}/assets/waypoint/applicant-icon-blue-01.png`, + inactiveImagePath: `${environment.baseHref}/assets/waypoint/applicant-icon-gray-01.png`, + id: 1, + isActive: false, + isComplete: false, + shouldBeShown: false, + comment: null, + displayNumber: 2 + }; + + private readonly collateralWayPoint = { + displayText: 'Collateral', + // TODO ( next PR ) these images can change depending on collateral type, for now $'s are fine + activeImagePath: `${environment.baseHref}/assets/waypoint/collateral-icon-blue.png`, + inactiveImagePath: `${environment.baseHref}/assets/waypoint/collateral-icon-gray.png`, + id: 2, + isActive: false, + isComplete: false, + shouldBeShown: false, + comment: null, + displayNumber: 2 + }; + + private readonly reviewWayPoint = { + displayText: 'Review', + activeImagePath: `${environment.baseHref}/assets/waypoint/review-icon-blue.png`, + inactiveImagePath: `${environment.baseHref}/assets/waypoint/review-icon-gray.png`, + id: 3, + isActive: false, + isComplete: false, + shouldBeShown: true, + comment: null, + displayNumber: 2 + }; + + private readonly finishWayPoint = { + displayText: 'Finish', + activeImagePath: `${environment.baseHref}/assets/waypoint/5-finish-active-icon.png`, + inactiveImagePath: `${environment.baseHref}/assets/waypoint/finish-icon-gray.png`, + id: 4, + isActive: false, + isComplete: false, + shouldBeShown: true, + comment: null, + displayNumber: 3 + }; + + + private readonly defaultWayPointSet = [this.applicantWayPoint, this.coapplicantWayPoint, + this.collateralWayPoint, this.reviewWayPoint, this.finishWayPoint]; + private waypointSet: BehaviorSubject> = new BehaviorSubject>(this.defaultWayPointSet); + public waypointSet$ = this.waypointSet.asObservable(); + + constructor() { } + + private updateDisplayNumbers() { + let counter = 1; + const newWayPoints = this.waypointSet.value.splice(0); + newWayPoints.forEach((waypoint) => { + if ( waypoint.shouldBeShown ) { + waypoint.displayNumber = counter; + counter++; + } + }, this); + this.waypointSet.next(newWayPoints); + } + + public handleActivePageRoute(route: string) { + if (route === 'step-1' || route === 'step-4') { + this.setStepActive(Waypoints.APPLICANT); + } else if (route.includes('co-applicant')) { + this.setStepActive(Waypoints.CO_APPLICANT); + } else if (route.includes('collateral')) { + this.setStepActive(Waypoints.COLLATERAL); + } else if (route === 'cc-disclosures' || route === 'cc-summary') { + this.setStepActive(Waypoints.REVIEW); + } else if (route === 'decision') { + this.setAllComplete(true); + } + } + + public showCoApplicant(shouldShow: boolean) { + const newWayPoints = this.waypointSet.value.splice(0); + newWayPoints[Waypoints.CO_APPLICANT].shouldBeShown = shouldShow; + this.waypointSet.next(newWayPoints); + this.updateDisplayNumbers(); + } + + public showCollateral(shouldShow: boolean) { + const newWayPoints = this.waypointSet.value.splice(0); + newWayPoints[Waypoints.COLLATERAL].shouldBeShown = shouldShow; + this.waypointSet.next(newWayPoints); + this.updateDisplayNumbers(); + } + + public setStepActive(id: number): void { + if (id < 0 || id > Waypoints.FINISH) { + throw new Error('Waypoint ID must be between 0 and ' + Waypoints.FINISH); + } + const newWaypoints = this.waypointSet.value.splice(0); + newWaypoints.forEach((waypoint, index) => { + waypoint.isActive = (newWaypoints[index].id === id); + }, this); + this.waypointSet.next(newWaypoints); + this.setCompleteUpToActive(); + } + + private setCompleteUpToActive(): void { + let activeFlag = false; + const newWaypoints = this.waypointSet.value.splice(0); + newWaypoints.forEach((waypoint, index) => { + if (waypoint.isActive) { + activeFlag = true; + waypoint.isComplete = false; + } else { + waypoint.isComplete = !activeFlag; + } + }, this); + this.waypointSet.next(newWaypoints); + } + + public setAllComplete(complete: boolean): void { + const newWaypoints = this.waypointSet.value.splice(0); + newWaypoints.forEach((waypoint) => { + waypoint.isActive = false; + waypoint.isComplete = complete; + }, this); + this.waypointSet.next(newWaypoints); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.ts new file mode 100644 index 0000000..bddbbf4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoint.ts @@ -0,0 +1,11 @@ +export interface Waypoint { + id: number; + displayText: string; + activeImagePath: string; + inactiveImagePath: string; + isActive: boolean; + isComplete: boolean; + shouldBeShown: boolean; + comment: string; + displayNumber: number; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.html new file mode 100644 index 0000000..4b03385 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.html @@ -0,0 +1,26 @@ + + +
+
+ +
+ +
{{waypoint.displayNumber}}
+
+ +
+ +
{{waypoint.displayNumber}}. {{waypoint.displayText}}
+
+ + + + +
{{waypoint.displayNumber}}. {{waypoint.displayText}}
+ +
+
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.scss new file mode 100644 index 0000000..c27b13d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.scss @@ -0,0 +1,177 @@ +$primary: #0054A2; +$secondary: #FBFAF9; +$success: #7DA454; +$white: #FFFFFF; +$gray-background: #DFDFDF; +$dark-gray-background: #999999; +$max-width: 770px; //at what point does it switch to 'mobile' view + +.md-stepper-horizontal { + display:table; + width:100%; + margin:0 auto; + background-color: $white; +} +.md-stepper-horizontal .md-step { + display:table-cell; + position:relative; + padding:24px; +} +.md-stepper-horizontal .md-step:active { + border-radius: 15% / 75%; +} +.md-stepper-horizontal .md-step:first-child:active { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.md-stepper-horizontal .md-step:last-child:active { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.md-stepper-horizontal .md-step:first-child .md-step-bar-left, +.md-stepper-horizontal .md-step:last-child .md-step-bar-right { + display:none; +} +.md-stepper-horizontal .md-step .md-step-circle { + display: none; + width:30px; + height:30px; + margin:0 auto; + bottom: 41px; + position: relative; + background-color: $dark-gray-background; + border-radius: 50%; + text-align: center; + line-height:30px; + font-size: 16px; + font-weight: 600; + color: $white; +} +.md-stepper-horizontal.green .md-step.active .md-step-circle { + background-color:#7ca550; +} + +.md-stepper-horizontal .md-step.editable .md-step-circle { + -moz-transform: scaleX(-1); + -o-transform: scaleX(-1); + -webkit-transform: scaleX(-1); + transform: scaleX(-1); +} +.md-stepper-horizontal .md-step.editable .md-step-circle:before { + font-family:'FontAwesome'; + font-weight:100; + content: "\f040"; +} +.md-stepper-horizontal .md-step .md-step-title { + margin-top:12px; + font-size:16px; + font-weight:600; +} +.md-stepper-horizontal .md-step .md-step-title, +.md-stepper-horizontal .md-step .md-step-optional { + text-align: center; + color:rgba(0,0,0,.26); +} +.md-stepper-horizontal .md-step.active .md-step-title { + font-weight: 600; + color:rgba(0,0,0,.87); +} +.md-stepper-horizontal .md-step.active.done .md-step-title, +.md-stepper-horizontal .md-step.active.editable .md-step-title { + font-weight:600; +} + +.md-stepper-horizontal .md-step.done .md-step-circle { + background-color: #7ca550; +} + +.md-stepper-horizontal .md-step .md-step-optional { + font-size:12px; +} +.md-stepper-horizontal .md-step.active .md-step-optional { + color:rgba(0,0,0,.54); +} +.md-stepper-horizontal .md-step .md-step-bar-left, +.md-stepper-horizontal .md-step .md-step-bar-right { + position:absolute; + bottom:95px; + height:1px; + border-top:3px solid #DDDDDD; +} +.md-stepper-horizontal .md-step .md-step-bar-right { + z-index: -1; + margin: 0 0 0 0; + color: $gray-background; + left: 50%; + height: 4px; + width: 90%; + top: 40%; + border: none; + background-color: $gray-background; +} +.md-stepper-horizontal .md-step .md-step-bar-left { + z-index: -1; + margin: 0 0 0 0; + color: $gray-background; + height: 4px; + width: 50%; + top: 40%; + border: none; + background-color: $gray-background; +} +.md-stepper-horizontal .md-step .md-step-bar-left.done, +.md-stepper-horizontal .md-step .md-step-bar-right.done { + background-color: $success !important; +} +.md-final-step-text-mobile { + display: none; +} +/*** Mobile view settings ***/ +@media screen and (max-width: $max-width) { + .md-stepper-horizontal .md-step .md-step-bar-right { + top: 32%; + width: 60%; + } + .md-stepper-horizontal .md-step .md-step-bar-left { + top: 32%; + width: 40%; + } + .md-step-image { + display: none !important; + } + .md-stepper-horizontal .md-step{ + padding: 2px; + } + .md-stepper-horizontal .md-step .md-step-circle { + display: block !important; + } + + .md-stepper-horizontal .md-step.active img{ + width: 50px !important; + } + + .md-stepper-horizontal .md-step .md-step-title { + font-size:12px; + } + + .md-final-step-image { + width: 50px !important; + } + .md-final-step-text { + display: none !important; + } + .md-final-step-text-mobile { + display: inherit !important; + } + // way-point fix for IE + @media screen and (-ms-high-contrast: none), (-ms-high-contrast: active) { + .md-stepper-horizontal .md-step .md-step-circle { + display: -ms-flexbox !important; + bottom: 35px !important; + right: 0 !important; + position: relative; + margin-top:50px; + justify-content: center; + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.spec.ts new file mode 100644 index 0000000..d0a0a39 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.spec.ts @@ -0,0 +1,72 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { WaypointsComponent } from './waypoints.component'; +import { Waypoints, WaypointService } from './waypoint.service'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Waypoints Component', () => { + let component: WaypointsComponent; + let fixture: ComponentFixture; + let service: WaypointService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + declarations: [WaypointsComponent] + }); + }); + + beforeEach(() => { + service = TestBed.get(WaypointService); + fixture = TestBed.createComponent(WaypointsComponent); + fixture.detectChanges(); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + expect(service).toBeTruthy(); + }); + + it('should render all initial provided steps ', () => { + let testData = null; + service.waypointSet$.subscribe((data) => testData = data); + const compiled = fixture.debugElement.nativeElement; + let steps = compiled.querySelectorAll('.md-step'); + expect( steps).toBeDefined(); + expect( steps.length).toEqual(3); + service.showCollateral(true); + fixture.detectChanges(); + steps = compiled.querySelectorAll('.md-step'); + expect( steps.length).toEqual(4); + service.showCoApplicant(true); + fixture.detectChanges(); + steps = compiled.querySelectorAll('.md-step'); + expect( steps.length).toEqual(testData.length); + }); + + it('should render the complete and done status of each step ', () => { + const compiled = fixture.debugElement.nativeElement; + let steps = compiled.querySelectorAll('.md-step.active'); + expect( steps).toBeDefined(); + expect( steps.length).toEqual(0); + service.setStepActive(Waypoints.REVIEW); + fixture.detectChanges(); + steps = compiled.querySelectorAll('.active'); + expect( steps).toBeDefined(); + expect( steps.length).toEqual(1); + steps = compiled.querySelectorAll('.md-step.done'); + expect( steps).toBeDefined(); + expect( steps.length).toEqual(1); + }); + + it('should render correct number and title for each step ', () => { + let testData = null; + service.waypointSet$.subscribe((data) => testData = data); + const compiled = fixture.debugElement.nativeElement; + let steps = compiled.querySelectorAll('.md-step-title'); + expect( steps).toBeDefined(); + expect( steps[0].textContent).toEqual(testData[0].displayNumber + '. ' + testData[0].displayText ); + steps = compiled.querySelectorAll('.md-step-circle span'); + expect( steps).toBeDefined(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.ts new file mode 100644 index 0000000..273bbde --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/layout/waypoints/waypoints.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { WaypointService } from './waypoint.service'; +import { Subscription } from 'rxjs'; +import { environment } from 'src/environments/environment'; +@Component({ + selector: 'lw-waypoints', + templateUrl: './waypoints.component.html', + styleUrls: ['./waypoints.component.scss'] +}) +export class WaypointsComponent implements OnInit, OnDestroy { + + public waypointSet = []; + public lastWaypoint = 0; + private subscription: Subscription; + public completedCheck = environment.baseHref + '/assets/waypoint/completed-check-icon.png'; + constructor(private waypointService: WaypointService) { } + + public ngOnInit(): void { + this.subscription = this.waypointService.waypointSet$.subscribe((waypointSet) => { + this.waypointSet = waypointSet; + this.lastWaypoint = waypointSet.length - 1; + }); + } + + public ngOnDestroy(): void { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-page.ts new file mode 100644 index 0000000..506e8b8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-page.ts @@ -0,0 +1,35 @@ +import { ValidatorFn } from '@angular/forms'; +import { ApplicationWizardService } from './application-wizard.service'; +import { CreditApplicationService } from '../shared/credit-application.service'; + +export interface ApplicationField { + field: string; + validation: Array; +} + +export interface ApplicationFieldGroup { + group: string; + fields: Array; +} + +export class ApplicationPage { + + public route: string; + public fieldgroup: Array; + public titleKey; + + public constructor(titleKey: string, route: string, formGroup: Array) { + this.titleKey = titleKey; + this.route = route; + this.fieldgroup = formGroup; + } + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + return true; + } + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + return ['ERROR']; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-wizard.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-wizard.service.spec.ts new file mode 100644 index 0000000..3ae6b63 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-wizard.service.spec.ts @@ -0,0 +1,236 @@ +import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../shared/credit-application-dao.service'; +import { ApplicationWizardService } from './application-wizard.service'; +import { MaskingService } from '../shared/masking/masking.service'; +import { MemberInfoFormComponent } from './field-groups/member/member-info-form.component'; +import { TextMaskModule } from 'angular2-text-mask'; +import { MemberInfoPageService } from './wizards/common/member/member-info-page.service'; +import { CommonGroups } from './field-groups/common-groups'; +import { LoanTypeSet } from '../shared/loan-type'; +import { of } from 'rxjs'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { CreditApplicationValues } from '../shared/credit-application-values'; +import { TranslateModule } from '@ngx-translate/core'; +import { BorrowerType } from '../shared/borrower-type'; +import { apiTranslatorPreTranslate } from '../testing/shared-mocks/api-translator-pre-post'; +import { EquityMemberInfoPageService } from './wizards/equity/equity-member-info-page.service'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../shared/credit-application.service'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.MEMBER_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('ApplicationWizardService', () => { + + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: CreditApplicationDaoService; + let creditApplicationService: CreditApplicationService; + let component: MemberInfoFormComponent; + let fixture: ComponentFixture; + let memberInfoPageService: MemberInfoPageService; + let equityMemberInfoPageService: EquityMemberInfoPageService; + let formBuilder: FormBuilder; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + declarations: [ + MemberInfoFormComponent + ], + schemas: [NO_ERRORS_SCHEMA], + providers: [] + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(CreditApplicationDaoService); + creditApplicationService = TestBed.get(CreditApplicationService); + memberInfoPageService = TestBed.get(MemberInfoPageService); + equityMemberInfoPageService = TestBed.get(EquityMemberInfoPageService); + fixture = TestBed.createComponent(MemberInfoFormComponent); + component = fixture.componentInstance; + component.group = createTestFormGroup(formBuilder); + }); + + it('should create', () => { + expect(applicationWizardService).toBeTruthy(); + }); + + it('should determine whether or not a page can be visited', () => { + const canVisitStep1: boolean = applicationWizardService.canVisitPage('step-1'); + const canVisitStep2: boolean = applicationWizardService.canVisitPage('step-2'); + expect(canVisitStep1).toBe(true); + expect(canVisitStep2).toBe(false); + }); + + + + it('should determine a page can be visited because there were no errors', () => { + spyOn(applicationWizardService, 'getFieldErrors').and.callThrough().and.returnValue([]); + const noErrors: boolean = applicationWizardService.canVisitPage('step-2'); + expect(noErrors).toBe(true); + }); + + it('should determine if there is a co-applicant', () => { + spyOn(MaskingService, 'unMaskNumber').and.callThrough().and.returnValue(1000); + spyOn(applicationWizardService, 'getFieldValue').and.callThrough(); + spyOn(memberInfoPageService, 'createMemberInformationGroup').and.callThrough(); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + component.group.patchValue({ + 'applicantType': CreditApplicationValues.CHOICE_YES, + 'limitRequested': '1000', + 'maritalStatus': CreditApplicationValues.CHOICE_NO + }); + creditApplicationService.saveMemberInformation(component.group.value); + const isCoApplicant: boolean = applicationWizardService.isCoApplicant(); + expect(isCoApplicant).toBe(true); + + component.group.patchValue({ + 'applicantType': CreditApplicationValues.CHOICE_NO, + 'limitRequested': '1000', + 'maritalStatus': CreditApplicationValues.CHOICE_NO + }); + + creditApplicationService.saveMemberInformation(component.group.value); + const changedCoApplicant: boolean = applicationWizardService.isCoApplicant(); + expect(changedCoApplicant).toBe(false); + }); + + it('should submit an application ', () => { + const application = applicationWizardService.getFullCreditApplication(); + spyOn(creditApplicationDaoService, 'submitApplication').and.returnValue(of(true)); + applicationWizardService.submitCreditApplication(application); + expect(creditApplicationDaoService.submitApplication).toHaveBeenCalled(); + }); + + it('should submit an application for decision', () => { + const application = applicationWizardService.getFullCreditApplication(); + spyOn(creditApplicationDaoService, 'decideApplication').and.returnValue(of(true)); + applicationWizardService.submitApplicationForDecision(application); + expect(creditApplicationDaoService.decideApplication).toHaveBeenCalled(); + }); + + it('should update an applications decision status', () => { + spyOn(creditApplicationDaoService, 'getApplicationDecision').and.returnValue(of(true)); + applicationWizardService.updateApplicationDecisionStatus(); + expect(creditApplicationDaoService.getApplicationDecision).toHaveBeenCalled(); + }); + + it('should return data for primary applicant', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + const primaryData1 = applicationWizardService.getIndividualGroupValues('employmentInformation', BorrowerType.PRIMARY_BORROWER); + expect(primaryData1).toEqual(apiTranslatorPreTranslate.fullInfoBorrowers[0].employmentInformation); + expect(Array.isArray(primaryData1)).toBeTruthy(); + }); + + it('should return data for secondary applicant', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + const primaryData1 = applicationWizardService.getIndividualGroupValues('otherIncomeInformation', BorrowerType.CO_BORROWER); + expect(primaryData1).toEqual(apiTranslatorPreTranslate.fullInfoBorrowers[1].otherIncomeInformation); + }); + + it('should return the applicant\'s first name', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + expect(applicationWizardService.getApplicantName(BorrowerType.PRIMARY_BORROWER)) + .toEqual(apiTranslatorPreTranslate.fullInfoBorrowers[0].personalInformation.firstName); + expect(applicationWizardService.getApplicantName(BorrowerType.CO_BORROWER)) + .toEqual(apiTranslatorPreTranslate.fullInfoBorrowers[1].personalInformation.firstName); + }); + + it('should return a field value w/ or w/o a group', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + expect(applicationWizardService.getFieldValue('loanType')).toEqual(apiTranslatorPreTranslate.loanType); + expect(applicationWizardService.getFieldValue('estimatedValue', 'collateralInformation')) + .toBe(apiTranslatorPreTranslate.collateralInformation.estimatedValue); + }); + + it('should determine that a loan needs collateral information', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + + // apiTranslatorPreTranslate is using Loan type 8, which does not require collateral. + // 8 used to be MANUFACTURED_HOME_LOAN which is commented out as of 2023. + expect(applicationWizardService.needsCollateral()).toBe(false); + }); + + it('should determine that a loan doesn\'t need collateral information', () => { + const creditCardApplication = apiTranslatorPreTranslate; + creditCardApplication.loanType = LoanTypeSet[0]; + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(creditCardApplication); + expect(applicationWizardService.needsCollateral()).toBe(false); + }); + + it('should return true if the co-borrower is married to the primary borrower', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + expect(applicationWizardService.isCoApplicantSpouse()).toBe(true); + }); + + it('should return false if the co-borrower is not married to the primary borrower', () => { + const applicantsAreNotMarriedToEachOther = apiTranslatorPreTranslate; + applicantsAreNotMarriedToEachOther.hasCoApplicantSpouse = null; + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(applicantsAreNotMarriedToEachOther); + expect(applicationWizardService.isCoApplicantSpouse()).toBe(false); + }); + + // tslint:disable-next-line:max-line-length + it('should return false if the applicant/co-applicant does not need to provide spouse information (if primary and co-borrower are not married to each other)', () => { + apiTranslatorPreTranslate.hasCoApplicantSpouse = CreditApplicationValues.CHOICE_YES; + apiTranslatorPreTranslate.fullInfoBorrowers[0].personalInformation.maritalStatus = CreditApplicationValues.CHOICE_YES; + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + spyOn(applicationWizardService, 'isCoApplicantSpouse').and.callThrough(); + expect(applicationWizardService.isApplicantNonSpouse(BorrowerType.CO_BORROWER)).toBe(false); + }); + + it('should return true if the applicant/co-applicant needs to provide spouse information', () => { + apiTranslatorPreTranslate.hasCoApplicantSpouse = CreditApplicationValues.CHOICE_NO; + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + spyOn(applicationWizardService, 'isCoApplicantSpouse').and.callThrough(); + expect(applicationWizardService.isApplicantNonSpouse(BorrowerType.PRIMARY_BORROWER)).toBe(true); + }); + + it('should return false if this is not a Manufactured Home', () => { + const mfgHomeNotOnRentedLotApplication = apiTranslatorPreTranslate; + mfgHomeNotOnRentedLotApplication.loanType = LoanTypeSet[4]; + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(mfgHomeNotOnRentedLotApplication); + expect(applicationWizardService.isRentedLotInfoRequired()).toBe(false); + expect(applicationWizardService.isLienPositionInfoRequired()).toBe(true); + }); + + it('should return group values', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + expect(applicationWizardService.getGroupValues('collateralInformation')).toEqual(apiTranslatorPreTranslate.collateralInformation); + expect(applicationWizardService.getGroupValues('someNonExistantGroup')).toEqual({}); + }); + + it('should return an individual from the application', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + expect(applicationWizardService.getIndividualByBorrowerType(BorrowerType.CO_BORROWER)) + .toEqual(apiTranslatorPreTranslate.fullInfoBorrowers[1]); + }); + + it('should correctly calculate a simple next route name', () => { + const service = applicationWizardService; + expect(service.getSimpleRouteName('https://applydev.landmarkcu.com/loan-wizard/cc/re/step-2')).toEqual('step-2'); + expect(service.getSimpleRouteName('https://a.x.com/lw/cc/re/step-1?fbclid=IwAR226oQp-ISjjS9U1I440bjA')).toEqual('step-1'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-wizard.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-wizard.service.ts new file mode 100644 index 0000000..0cb612b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/application-wizard.service.ts @@ -0,0 +1,353 @@ +import { Observable, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import { FormControl } from '@angular/forms'; +import { CreditApplication, Individual, SpouseType } from '../shared/credit-application'; +import { CreditApplicationDaoService } from '../shared/credit-application-dao.service'; +import { ApplicationPage } from './application-page'; +import { Injectable } from '@angular/core'; +import { CreditApplicationService } from '../shared/credit-application.service'; +import { CreditApplicationValues } from '../shared/credit-application-values'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { WaypointService } from '../layout/waypoints/waypoint.service'; +import { TranslateService } from '@ngx-translate/core'; +import { LoanTitle } from '../shared/loan-type/loan-title'; +import { LoanCategory } from '../shared/loan-type/loan-category'; +import { LoanCode } from '../shared/loan-type/loan-code'; + +@Injectable({ + providedIn: 'root' +}) +export class ApplicationWizardService { + + protected cachedFormControl = new FormControl(''); + // when using inheritance, the implied private field is not clear, so I've declared it implicitly. + protected creditApplicationDaoService: CreditApplicationDaoService; + + constructor(creditApplicationDaoService: CreditApplicationDaoService, + private creditApplicationService: CreditApplicationService, + private translateService: TranslateService, + private wayPointService: WaypointService) { + this.creditApplicationDaoService = creditApplicationDaoService; + } + + public setLoanType(loanType: string, parsedData?: any): void { + this.creditApplicationService.setLoanType(loanType, parsedData); + } + + public resetLoanType(): void { + this.creditApplicationService.resetLoanType(); + } + + public getApplicantName(borrowerType: BorrowerType): string { + const setIndividual = this.getIndividualByBorrowerType(borrowerType); + if (!setIndividual || !setIndividual.personalInformation.firstName) { + return 'Applicant'; + } + + return setIndividual.personalInformation.firstName; + } + + public getBeforeYouBeginMessage(): object { + const message = this.getFullCreditApplication().loanType.beforeYouBeginKey; + if(message) { + return this.translateService.get(message); + } else { + return null; + } + } + + public showProofOfIncomeUploadForm(): boolean { + const application = this.getFullCreditApplication(); + + if(application.loanType.apiCode === LoanCode.EQ_HELOC_Flex) { + return application.limitRequested > 100000 && application.loanType.showProofOfIncomeUploadForm === true + } + + return application.loanType.showProofOfIncomeUploadForm === true; + } + + public canVisitPage(pageRoute: string): boolean { + // You can't visit anything once you have a decision. + const creditApplication: CreditApplication = this.getFullCreditApplication(); + if (creditApplication && creditApplication.hasBeenCompleted) { + return pageRoute === 'decision'; + } + const pages = creditApplication.loanType.pages; + let currentPage = null; + for (let index = 0; index < pages.length; index++) { + currentPage = pages[index]; + if (currentPage.route === pageRoute) { + return true; + } else if (currentPage.determinePageRequired(this) && currentPage.isValidPage(this, this.creditApplicationService).length > 0) { + // redirect the user. + return false; + } + } + } + + public getFieldErrors(fieldName, value, validatorFnSet) { + let validationResult; + const errors = []; + validatorFnSet.forEach((validatorFn) => { + this.cachedFormControl.setValue(value); + validationResult = validatorFn(this.cachedFormControl); + if (validationResult) { + validationResult.field = fieldName; + errors.push(validationResult); + } + }); + return errors; + } + + public getFieldValue(field: string, group?: string) { + const creditApplication: CreditApplication = this.getFullCreditApplication(); + let value; + if (group) { + value = creditApplication[group]; + if (value) { + value = value[field]; + } + } else { + value = creditApplication[field]; + } + return value; + } + + public isCoApplicant(): boolean { + return this.getFieldValue('hasCoApplicant') === CreditApplicationValues.CHOICE_YES; + } + + public needsCollateral(): boolean { + return this.getFullCreditApplication().loanType.requiresCollateral; + } + + public isCoApplicantSpouse(): boolean { + return this.getFullCreditApplication().hasCoApplicantSpouse === CreditApplicationValues.CHOICE_YES; + } + + public isApplicantNonSpouse(individual: BorrowerType): boolean { + const setIndividual = this.getIndividualByBorrowerType(individual); + if (!setIndividual || this.isCoApplicantSpouse()) { + return false; + } + return setIndividual.addressInformation.state === CreditApplicationValues.STATE_REQUIRED_COAPP && + setIndividual.personalInformation.maritalStatus === CreditApplicationValues.CHOICE_YES; + } + + public isRentedLotInfoRequired(): boolean { + return this.getFullCreditApplication().loanType.title === LoanTitle.ManufacturedHome; + } + + public isLienPositionInfoRequired(): boolean { + if (this.getFullCreditApplication().loanType.category === LoanCategory.Equity) { + return this.getFullCreditApplication().loanType.title !== LoanTitle.ManufacturedHome; + } else { + return false; + } + } + + public showPayoffFirstMortgageStatementUploadForm(): boolean { + if (this.getFullCreditApplication().loanType.category === LoanCategory.Equity) { + return this.getFullCreditApplication().loanType.title === LoanTitle.HELOC || this.getFullCreditApplication().loanType.title === LoanTitle.HomeEquityFixed; + } else { + return false; + } + } + + public getSpouseType(borrowerType: BorrowerType): SpouseType { + const individual = this.getIndividualByBorrowerType(borrowerType); + if (borrowerType === BorrowerType.PRIMARY_BORROWER || (borrowerType === BorrowerType.CO_BORROWER && individual)) { + return individual.spouseType; + } + return null; + } + + public getGroupValues(group: string) { + if (!this.getFullCreditApplication()[group]) { + return {}; + } + return this.getFullCreditApplication()[group]; + } + + public getIndividualGroupValues(group: string, individual: BorrowerType) { + const selectedIndividual = this.getIndividualByBorrowerType(individual); + if (selectedIndividual && selectedIndividual[group]) { + return selectedIndividual[group]; + } + return {}; + } + + public getIndividualSpouse(individual: Individual): any { + if (individual.spouseType === SpouseType.FullInfoSpouse) { + return individual.fullInfoSpouse; + } else { + return individual.tattleSpouse; + } + } + + public getIndividualByBorrowerType(individual: BorrowerType) { + const creditApplication = this.getFullCreditApplication(); // DO NOT REMOVE THIS, tests will fail. + switch (individual) { + case BorrowerType.PRIMARY_BORROWER: + return creditApplication.fullInfoBorrowers[0]; + case BorrowerType.CO_BORROWER: + this.addDefaultCoApplicantIfNecessary(); + return creditApplication.fullInfoBorrowers[1]; + case BorrowerType.PRIMARY_BORROWER_SPOUSE: + return this.getIndividualSpouse(creditApplication.fullInfoBorrowers[0]); + case BorrowerType.CO_BORROWER_SPOUSE: + return this.getIndividualSpouse(creditApplication.fullInfoBorrowers[1]); + } + throw new Error('Invalid individual type in getIndividualByBorrowerType: ' + individual); + } + + public addDefaultCoApplicantIfNecessary() { + const creditApplication = this.getFullCreditApplication(); + if (creditApplication.hasCoApplicant === CreditApplicationValues.CHOICE_YES + && creditApplication.fullInfoBorrowers.length === 1) { + creditApplication.fullInfoBorrowers.push(this.creditApplicationService.generateDefaultFullInfoBorrower()); + if (creditApplication.coApplicantLivesInMaritalPropertyState) { + creditApplication.fullInfoBorrowers[1].addressInformation.state = CreditApplicationValues.STATE_REQUIRED_COAPP; + } + if (this.isCoApplicantSpouse()) { + creditApplication.fullInfoBorrowers[1].personalInformation.maritalStatus = CreditApplicationValues.CHOICE_YES; + } + } + } + + public getFullCreditApplication(): CreditApplication { + return this.creditApplicationService.getFullCreditApplication(); + } + + public submitCreditApplication(creditApplication: CreditApplication): Observable { + return this.creditApplicationDaoService.submitApplication(creditApplication) + .pipe( + map(result => { + if (result) { + const savedApplication = this.getFullCreditApplication(); + savedApplication.loanId = result['loanApplicationId']; + return true; + } + return false; + }), + catchError((err, caught) => { + return of(false); + }) + ); + } + + public submitApplicationForDecision(creditApplication: CreditApplication): Observable { + return this.creditApplicationDaoService.decideApplication(creditApplication) + .pipe( + map(result => { + return true; + }), + catchError((err, caught) => { + return of(false); + }) + ); + } + + public updateApplicationDecisionStatus(): Observable { + const creditApplication = this.getFullCreditApplication(); + return this.creditApplicationDaoService.getApplicationDecision(creditApplication) + .pipe( + map(result => { + if (result && result.state === 'Ready') { + creditApplication.decision = result; + return true; + } + return false; + }), + catchError((err, caught) => { + return of(false); + }) + ); + } + + public getPageIndexByRouteName(routeName: string): number { + const creditApplication = this.getFullCreditApplication(); + const pages = creditApplication.loanType.pages; + for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) { + if (pages[pageIndex].route === routeName) { + return pageIndex; + } + } + throw new Error('No page found for route name: ' + routeName); + } + + public getNextPageByIndex(currentIndex: number): ApplicationPage { + const creditApplication = this.getFullCreditApplication(); + const pages = creditApplication.loanType.pages; + for (let pageIndex = currentIndex + 1; pageIndex < pages.length; pageIndex++) { + if (pages[pageIndex].determinePageRequired(this)) { + return pages[pageIndex]; + } + } + return null; + } + + public getPreviousPageByIndex(currentIndex: number): ApplicationPage { + const creditApplication = this.getFullCreditApplication(); + const pages = creditApplication.loanType.pages; + for (let pageIndex = currentIndex - 1; pageIndex >= 0; pageIndex--) { + if (pages[pageIndex].determinePageRequired(this)) { + return pages[pageIndex]; + } + } + return null; + } + + public handlePostApplicationEvents(): void { + this.creditApplicationService.clearApplication(); + } + + public getImagePath(): string { + return this.getFullCreditApplication().loanType.imagePath; + } + + public getLoanMessage(): Observable { + const message = this.getFullCreditApplication().loanType.message; + return this.translateService.get(message); + } + + public getNextTopicMessage(routeName: string): string { + const currentPageIndex = this.getPageIndexByRouteName(routeName); + const nextPage = this.getNextPageByIndex(currentPageIndex); + if (nextPage) { + return nextPage.titleKey; + } + return 'NONEXTROUTE'; + } + + public getNextRoute(routeName: string): string { + const currentPageIndex = this.getPageIndexByRouteName(routeName); + const nextPage = this.getNextPageByIndex(currentPageIndex); + if (nextPage) { + return '../' + nextPage.route; + } + return null; + } + + public getPreviousRoute(routeName: string): string { + const currentPageIndex = this.getPageIndexByRouteName(routeName); + const prevPage = this.getPreviousPageByIndex(currentPageIndex); + if (prevPage) { + return '../' + prevPage.route; + } + return null; + } + + public displayWaypointsForPage(currentPage: string) { + this.wayPointService.showCoApplicant(this.isCoApplicant()); + this.wayPointService.showCollateral(this.needsCollateral()); + this.wayPointService.handleActivePageRoute(currentPage); + } + + public getSimpleRouteName(url: string): string { + const simpleurl = url.split('/').pop(); + if (simpleurl) { + return simpleurl.match(/[a-zA-Z0-9\-_]+/)[0]; + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.html new file mode 100644 index 0000000..49b011c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.html @@ -0,0 +1,12 @@ +
+
+
{{ ('ERRORS.CUSTOM.' + msgId) }}
+
+
+ +
+ {{eachKey.key}} +
+ +
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.scss new file mode 100644 index 0000000..e39da99 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.scss @@ -0,0 +1,5 @@ + +:host { + font-style: italic; + padding-top: 0.25em; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.spec.ts new file mode 100644 index 0000000..1c27849 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.spec.ts @@ -0,0 +1,39 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ErrorDisplayComponent } from './error-display.component'; +import { Component } from '@angular/core'; +import { Validators, FormControl, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +@Component({ + selector: 'lw-test-errordisplay', + template: '' +}) +class TestErrorDisplayComponent { + + public testControl = new FormControl('initialTestValue', + [Validators.required, Validators.maxLength(5)]); +} + +describe('Error Display Component', () => { + let component: TestErrorDisplayComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, + TranslateModule.forRoot()], + declarations: [ErrorDisplayComponent, TestErrorDisplayComponent] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TestErrorDisplayComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.ts new file mode 100644 index 0000000..6911a51 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.component.ts @@ -0,0 +1,54 @@ +import { Component, Input, HostBinding, OnDestroy, OnInit } from '@angular/core'; +import { AbstractControl } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { ErrorDisplayService, ErrorMessageEntry, ErrorMessageMap } from './error-display.service'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'lw-error-display', + templateUrl: './error-display.component.html', + styleUrls: ['./error-display.component.scss'] +}) +export class ErrorDisplayComponent implements OnInit, OnDestroy { + + @Input() public title: string; + @Input() public inputControl: AbstractControl; + @Input() public msgId: string; + @Input() public msgMap: ErrorMessageMap; + public messageKeySet: Array; + public subscription: Subscription; + + @HostBinding('class.invalid-feedback') showAsFeedback = true; + @HostBinding('class.col-md-10') showAsCol = true; + + constructor(private errorDisplayService: ErrorDisplayService, private translateService: TranslateService) { } + + public ngOnInit() { + if (this.title) { + this.title = this.translateService.instant(this.title); + this.translateService.get(this.title).subscribe((result) => { + this.title = result; + this.addMessageSetSubscription(); + }); + } else { + this.addMessageSetSubscription(); + } + } + + public addMessageSetSubscription(): void { + this.subscription = this.inputControl.valueChanges.subscribe(() => { + if (this.inputControl) { + this.messageKeySet = this.errorDisplayService.getErrorsForControl(this.title, this.inputControl, this.msgMap); + } else { + this.messageKeySet = []; + } + }); + this.messageKeySet = this.errorDisplayService.getErrorsForControl(this.title, this.inputControl, this.msgMap); + } + + public ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.service.spec.ts new file mode 100644 index 0000000..bd605c0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.service.spec.ts @@ -0,0 +1,53 @@ +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; +import { ErrorDisplayService } from './error-display.service'; + + +describe('Error Display Service', () => { + + let service: ErrorDisplayService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + service = TestBed.get(ErrorDisplayService); + }); + + it('should return a set for an invalid control', () => { + const control1 = new FormControl('', [Validators.required]); + const YODA_KEY = 'required, test is.'; + const TITLE = 'testTitle'; + expect(control1.valid).toEqual(false); + const result = service.getErrorsForControl(TITLE, control1, {'required': YODA_KEY}); + expect(result).toBeTruthy(); + expect(result.length).toEqual(1); + expect(result[0].key).toEqual(YODA_KEY); + expect(result[0].title).toEqual(TITLE); + }); + + it('should return an empty set for valid control', () => { + const control1 = new FormControl('ICanHazValue', [Validators.required]); + const YODA_KEY = 'required, test is.'; + const TITLE = 'testTitle'; + expect(control1.valid).toEqual(true); + const result = service.getErrorsForControl(TITLE, control1, {'required': YODA_KEY}); + expect(result).toBeTruthy(); + expect(result.length).toEqual(0); + }); + + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.service.ts new file mode 100644 index 0000000..bdbfbdb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/error-display/error-display.service.ts @@ -0,0 +1,128 @@ +import { Injectable } from '@angular/core'; +import { AbstractControl } from '@angular/forms'; +import { FormatService } from '../../../shared/pipes/format.service'; + +export interface ErrorMessageMap { + required?: string; + minlength?: string; + maxlength?: string; + min?: string; + max?: string; + email?: string; + pattern?: string; +} + +export interface ErrorMessageEntry { + key?: string; + title?: string; + value?: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class ErrorDisplayService { + + constructor(private formatService: FormatService) {} + + public errorSet = [ + { + 'errorType': 'required', + 'messageKey': 'ERRORS.REQUIRED' + }, + { + 'errorType': 'minlength', + 'messageKey': 'ERRORS.MINLENGTH', + 'valueMap': inputControl => inputControl.errors.minlength.requiredLength + }, + { + 'errorType': 'maxlength', + 'messageKey': 'ERRORS.MAXLENGTH', + 'valueMap': inputControl => inputControl.errors.maxlength.requiredLength + }, + { + 'errorType': 'min', + 'messageKey': 'ERRORS.MINVAL', + 'valueMap': inputControl => inputControl.errors.min.min + }, + { + 'errorType': 'max', + 'messageKey': 'ERRORS.MAXVAL', + 'valueMap': inputControl => inputControl.errors.max.max + }, + { + 'errorType': 'maxCurrency', + 'messageKey': 'ERRORS.MAX_AMOUNT_CURRENCY', + 'valueMap': inputControl => this.formatService.formatCurrencyNoDecimal(inputControl.errors.maxCurrency.max) + }, + { + 'errorType': 'loanValueMinimum', + 'messageKey': 'ERRORS.MINLOANAMT', + 'valueMap': inputControl => this.formatService.formatCurrencyNoDecimal(inputControl.errors.loanValueMinimum.min) + }, + { + 'errorType': 'loanValueMaximum', + 'messageKey': 'ERRORS.MAXLOANAMT', + 'valueMap': inputControl => this.formatService.formatCurrencyNoDecimal(inputControl.errors.loanValueMaximum.max) + }, + { + 'errorType': 'incomeValueMinimum', + 'messageKey': 'ERRORS.MININCAMT', + 'valueMap': inputControl => inputControl.errors.incomeValueMinimum.min + }, + { + 'errorType': 'incomeValueMaximum', + 'messageKey': 'ERRORS.MAXINCAMT', + 'valueMap': inputControl => inputControl.errors.incomeValueMaximum.max + }, + {'errorType': 'email', 'messageKey': 'ERRORS.EMAIL'}, + {'errorType': 'pattern', 'messageKey': 'ERRORS.PATTERN'}, + { + 'errorType': 'requiredState', + 'messageKey': 'ERRORS.REQUIREDSTATE', + 'valueMap': inputControl => inputControl.errors.requiredState.state + }, + { + 'errorType': 'requireWisconsinCollateral', + 'messageKey': 'ERRORS.REQUIRE_WI_COLLATERAL' + }, + { + 'errorType': 'sameValue', + 'messageKey': 'ERRORS.CUSTOM.SAME_VALUE', + 'valueMap': inputControl => inputControl.errors.sameValue + }, + { + 'errorType': 'invalidFiles', + 'messageKey': 'ERRORS.CUSTOM.INVALID_FILES', + 'valueMap': inputControl => inputControl.errors.invalidFiles + }, + { + 'errorType': 'titleBranded', + 'messageKey': 'ERRORS.CUSTOM.TITLE_BRANDED', + 'valueMap': inputControl => inputControl.errors.titleBranded + } + ]; + + public getErrorsForControl(title: string, control: AbstractControl, msgMap: ErrorMessageMap): Array { + const messageKeySet = []; + let currentKey = null; + this.errorSet.forEach((errorPair) => { + if (control.errors && control.errors[errorPair.errorType]) { + if (msgMap && msgMap[errorPair.errorType]) { + currentKey = msgMap[errorPair.errorType]; + } else { + currentKey = errorPair.messageKey; + } + messageKeySet.push( + { + 'key': currentKey, + 'title': title, + 'value': errorPair.valueMap ? errorPair.valueMap(control) : null + }); + } + }, this); + return messageKeySet; + } + +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.html new file mode 100644 index 0000000..1ba8417 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.html @@ -0,0 +1,6 @@ + + + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.scss new file mode 100644 index 0000000..513dea2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.scss @@ -0,0 +1,45 @@ +:host { + position: absolute; + right: 2rem; + color: gray; + bottom: .15rem; +} +.svg-inline--fa { + vertical-align: .125rem !important; +} + +.error { + color: red; +} + +.success { + color: green; +} + +.input-auto + :host, +.input-lmcu-fixed-width + :host { + position: inherit; + bottom: -0.15rem; +} +// dropdown needs more room +.custom-drop-down :host { + right: 3.5rem !important; +} + +// input with an icon on the right needs more room too +.custom-input-right-icon :host { + right: 3.5rem !important; +} + +svg { + background-color: white; +} + +@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { + // alignment for IE + .custom-drop-down + :host { + position: inherit; + bottom: 0.5rem; + right: 3.5rem; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.spec.ts new file mode 100644 index 0000000..c0d8b7a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.spec.ts @@ -0,0 +1,38 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Component } from '@angular/core'; +import { Validators, FormControl, ReactiveFormsModule } from '@angular/forms'; +import { InputIconComponent } from './input-icon.component'; +import { configureTestSuite } from 'ng-bullet'; + +@Component({ + selector: 'lw-test-inputicon', + template: '' +}) +class TestInputIconComponent { + + public testControl = new FormControl('initialTestValue', + [Validators.required, Validators.maxLength(5)]); +} + +describe('Error Display Component', () => { + let component: TestInputIconComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ReactiveFormsModule], + declarations: [InputIconComponent, TestInputIconComponent] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TestInputIconComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.ts new file mode 100644 index 0000000..a3137b0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/controls/input-icon/input-icon.component.ts @@ -0,0 +1,18 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup, AbstractControl } from '@angular/forms'; + +@Component({ + selector: 'lw-input-icon', + templateUrl: './input-icon.component.html', + styleUrls: ['./input-icon.component.scss'] +}) +export class InputIconComponent implements OnInit { + + @Input() + public inputControl: AbstractControl; + + constructor() { } + + public ngOnInit() { + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.html new file mode 100644 index 0000000..f03e829 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.html @@ -0,0 +1,103 @@ +
+
+ +
+
+ ADDRESS_COMPARE.UNABLE_TO_VERIFY +
+
+ ADDRESS_COMPARE.CONFIRM +
+
+
+
+
+
+
ADDRESS_COMPARE.USE_MINE
+ +
+
+
+
ADDRESS_COMPARE.SELECT_MATCHING
+ +
+
+
+
+
+ +
+
+
+ +
+
+ ADDRESS_COMPARE.UNABLE_TO_FIND_MATCH_LINE1 +
+
+ ADDRESS_COMPARE.UNABLE_TO_FIND_MATCH_LINE2 +
+
+
+
+
+
+
ADDRESS_COMPARE.USE_MINE
+ +
+
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.spec.ts new file mode 100644 index 0000000..1639ca0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.spec.ts @@ -0,0 +1,157 @@ +import { TestBed, async, ComponentFixture, tick, fakeAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { AddressCompareInfoFormComponent } from './address-compare-info-form.component'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { AddressValidationService } from 'src/app/shared/address-validation/address-validation.service'; +import { AddressCompareInfoFormService } from './address-compare-info-form.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ApiAddress } from 'src/app/shared/api-models/api-address'; +import { AddressInformation } from '../address/address-information'; +import { AddressDiffInformation } from './address-compare-information'; +import { of } from 'rxjs'; + +const createTestAddressFormGroup = (formBuilder: FormBuilder): FormGroup => { + const memberGroup = formBuilder.group({}); + + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; +}; + +describe('AddressCompareInfoFormComponent', () => { + let component: AddressCompareInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let addressValidationService: AddressValidationService; + let service: AddressCompareInfoFormService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + AddressCompareInfoFormComponent + ], + providers: [ + AddressValidationService + ] + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestAddressFormGroup(formBuilder); + service = TestBed.get(AddressCompareInfoFormService); + addressValidationService = TestBed.get(AddressValidationService); + fixture = TestBed.createComponent(AddressCompareInfoFormComponent); + component = fixture.componentInstance; + component.addressInfoGroup = formGroup; + }); + + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + // tslint:disable-next-line:max-line-length + it('should create a VALID FormGroup (with default value for the verified address) if we get a good response from address validation service', fakeAsync(() => { + const apiAddress: ApiAddress = { + address1: '1980 OurHouseInTheMiddleOfOur St.', + address2: 'APT 39', + city: 'Madness', + state: 'UK', + postalCode: '54545', + postalCodeExt: '4444' + }; + const uiAddress: AddressInformation = { + addressLine1: '1980 OurHouseInDaMiddleOfOur St.', + addressLine2: 'Apt 39', + zipcode: '54545', + city: 'Madness', + state: 'UK' + }; + + const diff: AddressDiffInformation = { + address1: false, + address2: true, + city: true, + state: true, + postalCode: true, + allFieldsSame: false + }; + + spyOn(addressValidationService, 'getValidatedAddress') + .and.returnValue( + of(apiAddress) + ); + + fixture.detectChanges(); + + component.addressInfoGroup.patchValue(uiAddress); + + component.addressInfoGroup.updateValueAndValidity(); + tick(); + fixture.detectChanges(); + + expect(component.selectionInfoGroup).toBeTruthy(); + expect(component.selectionInfoGroup.valid).toBeTruthy(); + expect(component.selectionInfoGroup.value.addressSelection).toBe('compareAddress'); + expect(component.addressCompareInformation).toEqual({ + compareAddress: apiAddress, + userInput: uiAddress, + addressDiff: diff + }); + })); + + // tslint:disable-next-line:max-line-length + it('should create an INVALID FormGroup (with no default value for the verified address) if we get a NOBODY response from address validation service', fakeAsync(() => { + + const uiAddress: AddressInformation = { + addressLine1: '1980 OurHouseInDaMiddleOfOur St.', + addressLine2: 'Apt 39', + zipcode: '54545', + city: 'Madness', + state: 'UK' + }; + + spyOn(addressValidationService, 'getValidatedAddress') + .and.returnValue( + of(null) + ); + + fixture.detectChanges(); + + component.addressInfoGroup.patchValue(uiAddress); + + component.addressInfoGroup.updateValueAndValidity(); + tick(); + fixture.detectChanges(); + + expect(component.selectionInfoGroup).toBeTruthy(); + expect(component.selectionInfoGroup.valid).toBeFalsy(); + expect(component.selectionInfoGroup.value.addressSelection).toBe(null); + expect(component.addressCompareInformation).toEqual({ + compareAddress: null, + userInput: uiAddress, + addressDiff: null + }); + })); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.ts new file mode 100644 index 0000000..1364256 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.component.ts @@ -0,0 +1,145 @@ +import { Component, Input, OnInit, OnDestroy, Output, EventEmitter } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { AddressCompareInformation } from './address-compare-information'; +import { NgbPanelChangeEvent } from '@ng-bootstrap/ng-bootstrap'; +import { Subscription } from 'rxjs'; +import { AddressValidationFormatConverter } from 'src/app/shared/address-validation/address-validation-format-converter'; +import { AddressInformation } from '../address/address-information'; +import { AddressCompareInfoFormService } from './address-compare-info-form.service'; +import { ApiAddress } from 'src/app/shared/api-models/api-address'; + +@Component({ + selector: 'lw-address-compare-info-form', + templateUrl: './address-compare-info-form.component.html', + styleUrls: ['./address-compare.scss'] + +}) +export class AddressCompareInfoFormComponent implements OnInit, OnDestroy { + + private parentStatusSubscription: Subscription; + private compareStatusSubscription: Subscription; + + private readonly FORM_STATE_INVALID = 'INVALID'; + private readonly COMPARE_ADDRESS_FIELD = 'compareAddress'; + private readonly USER_INPUT_FIELD = 'userInput'; + + @Input() + public addressInfoGroup: FormGroup; + + @Output() + public sendAcceptedAddress: EventEmitter; + + public addressCompareInformation: AddressCompareInformation; + public selectionInfoGroup: FormGroup; + + public localState = { + showNoMatch: false, + confirmDisabled: true + }; + + constructor(private addressCompareInfoFormService: AddressCompareInfoFormService) { + this.sendAcceptedAddress = new EventEmitter(); + } + + public beforeChange(e: NgbPanelChangeEvent) { + if (e.panelId === 'address-compare-panel') { + e.preventDefault(); + } + } + + public ngOnInit() { + this.parentStatusSubscription = this.addressInfoGroup.statusChanges.subscribe(this.onStatusChange.bind(this)); + } + + public ngOnDestroy() { + if (this.parentStatusSubscription) { + this.parentStatusSubscription.unsubscribe(); + } + if (this.compareStatusSubscription) { + this.compareStatusSubscription.unsubscribe(); + } + } + + public onStatusChange(newStatus: string): void { + this.parentStatusSubscription.unsubscribe(); + if (newStatus !== this.FORM_STATE_INVALID) { + const result = this.addressCompareInfoFormService.requestAddressCompare(this.addressInfoGroup.value); + + result.subscribe((compareAddress: ApiAddress) => { + if (!compareAddress) { + this.handleNoMatchFound(); + } else { + this.handleMatchFound(compareAddress); + } + }); + } else { + this.selectionInfoGroup = null; + this.addressCompareInformation = null; + } + this.parentStatusSubscription = this.addressInfoGroup.statusChanges.subscribe(this.onStatusChange.bind(this)); + } + + public handleNoMatchFound(): void { + this.addSelectionGroup(); + this.localState.showNoMatch = true; + this.selectionInfoGroup.patchValue({addressSelection: null}); + this.addressCompareInformation = { + userInput: this.addressInfoGroup.value, + compareAddress: null, + addressDiff: null + }; + this.sendAcceptedAddress.emit(null); + } + + public handleMatchFound(compareAddress: ApiAddress): void { + this.addressCompareInformation = { + userInput: this.addressInfoGroup.value, + compareAddress: compareAddress, + addressDiff: AddressValidationFormatConverter.getAddressValidationDiff(compareAddress, this.addressInfoGroup.value) + }; + + if (!this.selectionInfoGroup && !this.addressCompareInformation.addressDiff.allFieldsSame) { + this.addSelectionGroup(); + this.sendAcceptedAddress.emit(null); + } else if (this.addressCompareInformation.addressDiff.allFieldsSame) { + this.addressCompareInformation = null; + this.selectionInfoGroup = null; + if (this.compareStatusSubscription) { + this.compareStatusSubscription.unsubscribe(); + } + this.sendAcceptedAddress.emit(AddressValidationFormatConverter.makeUiAddress(compareAddress)); + } + + if (this.selectionInfoGroup) { + this.selectionInfoGroup.patchValue({addressSelection: this.COMPARE_ADDRESS_FIELD}); + } + this.localState.showNoMatch = false; + } + + public addSelectionGroup() { + this.selectionInfoGroup = this.addressCompareInfoFormService.createSelectionGroup(); + this.compareStatusSubscription = this.selectionInfoGroup.statusChanges + .subscribe(newStatus => this.localState.confirmDisabled = newStatus === this.FORM_STATE_INVALID); + } + + public commandSendAddressConfirmation() { + const formattedMessage: AddressInformation = this.getValueFromCompareSelection(this.selectionInfoGroup.value.addressSelection); + if (formattedMessage) { + this.sendAcceptedAddress.emit(formattedMessage); + this.addressCompareInformation = null; + this.selectionInfoGroup = null; + } + } + + + public getValueFromCompareSelection(field: string): AddressInformation | null { + if (!field) { + return null; + } else if (field === this.USER_INPUT_FIELD) { + return this.addressCompareInformation.userInput; + } else if (field === this.COMPARE_ADDRESS_FIELD) { + return AddressValidationFormatConverter.makeUiAddress(this.addressCompareInformation.compareAddress); + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.service.spec.ts new file mode 100644 index 0000000..cc7d94d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.service.spec.ts @@ -0,0 +1,38 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConfigurationService } from '../../../shared/configuration/configuration.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddressCompareInfoFormService } from './address-compare-info-form.service'; +import { AddressValidationService } from 'src/app/shared/address-validation/address-validation.service'; + +describe('Vehicle Collateral Info Form Service', () => { + + let service: AddressCompareInfoFormService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + AddressValidationService, + ConfigurationService + ] + }).compileComponents(); + + service = TestBed.get(AddressCompareInfoFormService); + })); + + it('should create', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.service.ts new file mode 100644 index 0000000..0ca0a91 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-info-form.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { AddressValidationService } from 'src/app/shared/address-validation/address-validation.service'; +import { AddressInformation } from '../address/address-information'; +import { Observable } from 'rxjs'; +import { AddressValidationFormatConverter } from 'src/app/shared/address-validation/address-validation-format-converter'; + +@Injectable({ + providedIn: 'root' +}) +export class AddressCompareInfoFormService { + + public static readonly ADDRESS_FORM_NAME = 'addressInformation'; + public static readonly COMPARE_ADDRESS_FORM_NAME = 'compareAddressInformation'; + + constructor( + private addressValidationService: AddressValidationService, + private formBuilder: FormBuilder) { + } + + public createSelectionGroup(): FormGroup { + const addressCompareGroup = this.formBuilder.group({}); + + CommonGroups.COMPARE_ADDRESS_INFO.fields.forEach((eachField) => { + addressCompareGroup.addControl(eachField.field, this.formBuilder.control('')); + }, this); + + CommonGroups.COMPARE_ADDRESS_INFO.fields.forEach((eachField) => { + addressCompareGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + return addressCompareGroup; + } + + public requestAddressCompare(addressInformation: AddressInformation): Observable { + return this.addressValidationService.getValidatedAddress(AddressValidationFormatConverter.makeApiAddress(addressInformation)); + } + + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-information.ts new file mode 100644 index 0000000..0477ada --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare-information.ts @@ -0,0 +1,17 @@ +import { AddressInformation } from '../address/address-information'; +import { ApiAddress } from 'src/app/shared/api-models/api-address'; + +export interface AddressCompareInformation { + userInput: AddressInformation; + compareAddress: ApiAddress; + addressDiff: AddressDiffInformation; +} + +export interface AddressDiffInformation { + address1: boolean; + address2?: boolean; + city: boolean; + state: boolean; + postalCode: boolean; + allFieldsSame: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare.scss new file mode 100644 index 0000000..27ac148 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address-compare/address-compare.scss @@ -0,0 +1,80 @@ +$error: #f63440; + +.horizontal-divide { + margin: 0 0 2em 0; + color: #EAEAEA; + height: 1px; + border: none; + background-color: #EAEAEA; +} + +.btn { + margin-left : 4px; +} + +.confirm { + margin-left : 0px; + margin-bottom: 0px; +} + +.address-select-card { + text-align: left; +} + +.address-select-radio { + float: left; + margin-top: 20px; + margin-bottom: 20px; + margin-right: 6px; + width: 10%; +} + +.address-select-btn, .address-select-btn:visited, .address-select-btn:hover { + padding-left: 30px !important; + padding-right: 10%; + border-radius: 10px; +} + +.btn-group-child { + width: 300px; +} + +@media(max-width: 768px) { + .btn-group-child { + width: unset; + max-width: 300px; + } +} + +.address-select-btn:active, .address-select-btn.active { + color: #7C7C7C; + background-color: #E5EEF7; +} + +#address-selection { + box-sizing: none; +} + +.address-field-diff { + color: $error; + border: 1px $error solid; + padding: 2px; +} + +// Card +.address-compare-panel { + border: 1px solid #0D3151; + border-radius: 5px; + margin-bottom: 15px; +} +// Card Headline +.address-compare-panel-header { + position: relative; + background-color: #0D3151; + color: #ffffff; + font-size: 1.2rem; + letter-spacing: 1.5px; + border-radius: 0; + height: 50px; + padding: 12px; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.html new file mode 100644 index 0000000..1760d71 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.html @@ -0,0 +1,82 @@ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.spec.ts new file mode 100644 index 0000000..3d24d28 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.spec.ts @@ -0,0 +1,112 @@ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {RouterTestingModule} from '@angular/router/testing'; + +import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; +import {AddressInfoFormComponent} from './address-info-form.component'; +import {InputIconComponent} from '../../controls/input-icon/input-icon.component'; +import {ErrorDisplayComponent} from '../../controls/error-display/error-display.component'; +import {FormBuilder, FormGroup, ReactiveFormsModule} from '@angular/forms'; +import {CommonGroups} from '../common-groups'; +import {ConfigurationService} from '../../../shared/configuration/configuration.service'; +import {AddressInformation} from './address-information'; +import {By} from '@angular/platform-browser'; +import {TextMaskModule} from 'angular2-text-mask'; +import {TranslateModule} from '@ngx-translate/core'; +import { AutoFocusDirective } from '../../../shared/page-focus/auto-focus.directive'; +import {StatePipe} from '../../../shared/pipes/state.pipe'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {UiSettings} from '../../../shared/configuration/ui-settings'; +import {DefaultSettings} from '../../../shared/configuration/default-settings.mock'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const memberGroup = formBuilder.group({}); + + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; +}; + +describe('AddressInfoFormComponent', () => { + let component: AddressInfoFormComponent; + let fixture: ComponentFixture; + let service: ConfigurationService; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: AddressInformation; + let status: string; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + AddressInfoFormComponent, + AutoFocusDirective, + StatePipe + ], + providers: [ + ConfigurationService + ] + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(AddressInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + service = TestBed.get(ConfigurationService); + service.saveConfiguration(defaultSettings); + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should load the list of states to select', () => { + expect(service).toBeTruthy(); + spyOn(service, 'getConfigurationByField').and.returnValue([{0: 'AA'}]); + fixture.detectChanges(); + expect(service.getConfigurationByField).toHaveBeenCalled(); + expect(component.stateSet[0]).toEqual({0: 'AA'}); + }); + + it('should emit changes', () => { + component.group.patchValue({'city': 'Milwaukee', 'addressLine1': '100 East Wisconsin'}); + + expect(changes.city).toBe('Milwaukee'); + expect(changes.addressLine1).toBe('100 East Wisconsin'); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({'city': 'Milwaukee', 'addressLine1': '100 East Wisconsin', 'PO Box#': 706}); + expect(changes['PO Box#']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + component.group.patchValue({'city': 'Milwaukee', 'addressLine1': '100 East Wisconsin'}); + expect(status).toBe('INVALID'); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.ts new file mode 100644 index 0000000..2c6fc9a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-info-form.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, OnInit, ChangeDetectorRef, Pipe, PipeTransform } from '@angular/core'; +import { FormGroup, Validators } from '@angular/forms'; +import { ConfigurationService } from '../../../shared/configuration/configuration.service'; +import { MaskingService } from '../../../shared/masking/masking.service'; + +@Component({ + selector: 'lw-address-info-form', + templateUrl: './address-info-form.component.html' +}) +export class AddressInfoFormComponent implements OnInit { + + public zipMask = MaskingService.zipMask; + public numberMask = MaskingService.numberMask; + public stateSet = []; + + @Input() + public group: FormGroup; + @Input() + public idPrefix: string; + + @Input() + public isPrimaryResidenceInput: boolean; + + constructor(private configurationService: ConfigurationService) {} + + public ngOnInit() { + this.stateSet = this.configurationService.getConfigurationByField('stateOptions'); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-information.ts new file mode 100644 index 0000000..02a8d72 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/address-information.ts @@ -0,0 +1,8 @@ +export interface AddressInformation { + addressLine1: string; + addressLine2?: string; + city: string; + state: string; + zipcode: string; + zipcodeExt?: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.html new file mode 100644 index 0000000..95c5ba0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.html @@ -0,0 +1,20 @@ +
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.spec.ts new file mode 100644 index 0000000..7305ac7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.spec.ts @@ -0,0 +1,90 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { IsAddressSameInfoFormComponent } from './is-address-same-info-form.component'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { DataTableService } from '../../../shared/data-table.service'; +import { IsAddressSameInformation } from './is-address-same-information'; +import { By } from '@angular/platform-browser'; +import {TranslateModule} from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const memberGroup = formBuilder.group({}); + + CommonGroups.IS_ADDRESS_SAME_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; +}; + +describe('IsAddressSameInfoFormComponent', () => { + let component: IsAddressSameInfoFormComponent; + let fixture: ComponentFixture; + let service: DataTableService; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: IsAddressSameInformation; + let status: string; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + IsAddressSameInfoFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(IsAddressSameInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + service = TestBed.get(DataTableService); + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit changes', () => { + component.group.patchValue({'isAddressSame': 'yes'}); + expect(changes.isAddressSame).toBe('yes'); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({'isMailingAddressSame': false}); + expect(changes['isMailingAddressSame']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + expect(status).toBe('INVALID'); + }); + + it('should emit VALID for complete form', () => { + component.group.patchValue({'isAddressSame': 'yes'}); + expect(status).toBe('VALID'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.ts new file mode 100644 index 0000000..4316ed5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-info-form.component.ts @@ -0,0 +1,25 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-is-address-same-info-form', + templateUrl: './is-address-same-info-form.component.html' +}) +export class IsAddressSameInfoFormComponent implements OnInit { + + public stateSet = { + coApp: 'FORMS.SAME_ADDRESS.CO_APP', + heloc: 'FORMS.SAME_ADDRESS.HELOC', + spouse: 'FORMS.SAME_ADDRESS.SPOUSE' + }; + + @Input() + public group: FormGroup; + + @Input() + public message: string; + + constructor() {} + + public ngOnInit() {} +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-information.ts new file mode 100644 index 0000000..6996085 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/is-address-same-information.ts @@ -0,0 +1,3 @@ +export interface IsAddressSameInformation { + isAddressSame: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.html new file mode 100644 index 0000000..3c3614a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.html @@ -0,0 +1,87 @@ +
+
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ + +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
+ +
+
+
+ + +
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.spec.ts new file mode 100644 index 0000000..b0b7c68 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.spec.ts @@ -0,0 +1,49 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormBuilder } from '@angular/forms'; +import { DataTableService } from '../../../shared/data-table.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ResidenceInfoFormComponent } from './residence-info-form.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +describe('ResidenceInfoFormComponent', () => { + let component: ResidenceInfoFormComponent; + let fixture: ComponentFixture; + let service: DataTableService; + let formBuilder: FormBuilder; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + ResidenceInfoFormComponent + ], + schemas: [ + NO_ERRORS_SCHEMA + ] + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + fixture = TestBed.createComponent(ResidenceInfoFormComponent); + component = fixture.componentInstance; + service = TestBed.get(DataTableService); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.ts new file mode 100644 index 0000000..cb8bafc --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-info-form.component.ts @@ -0,0 +1,35 @@ +import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MaskingService } from '../../../shared/masking/masking.service'; +import { SelectOptions } from '../../../shared/options/select-options'; + +@Component({ + selector: 'lw-residence-info-form', + templateUrl: './residence-info-form.component.html' +}) +export class ResidenceInfoFormComponent implements OnInit { + public numberMask = MaskingService.numberMask; + public numberMaskDecimal = MaskingService.numberMaskWithDecimal; + + @Input() + public group: FormGroup; + + @Input() + public isPrimaryResidenceInput: boolean; + + @Input() + public idPrefix: string; + + @Input() + public isHelocRateMod: boolean; + + public residenceStatusOptions = []; + public residenceYearOptions = SelectOptions.yearOptions; + public residenceMonthOptions = SelectOptions.monthOptions; + + constructor() {} + + public ngOnInit() { + this.residenceStatusOptions = this.isPrimaryResidenceInput ? SelectOptions.residenceStatusPresent : SelectOptions.residenceStatusPast; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-information.ts new file mode 100644 index 0000000..f31c952 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/address/residence-information.ts @@ -0,0 +1,7 @@ +export interface ResidenceInformation { + residenceStatus: string; + residenceMonthlyPayment?: number; + residenceTimeYears: string; + residenceTimeMonths: string; + isMailingAddressSame?: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/common-groups.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/common-groups.ts new file mode 100644 index 0000000..5b19301 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/common-groups.ts @@ -0,0 +1,533 @@ +import {Validators} from '@angular/forms'; +import {WizardValidators} from './wizard-validators'; +import {LoanLimitsService} from '../../shared/loan-limit/loan-limits.service'; +import { LoanCategory } from 'src/app/shared/loan-type/loan-category'; +import {IncomeLimitsService} from '../../shared/income-limit/income-limits.service'; +import {CreditApplicationValues} from '../../shared/credit-application-values'; +import { LoanTitle } from 'src/app/shared/loan-type/loan-title'; + +export class CommonGroups { + + private static PHONE_PATTERN = /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/; + private static SSN_PATTERN = /^\d{3}-\d{2}-\d{4}$/; + private static SSN_LAST_FOUR = /^[0-9]{4}$/; + private static ZIPCODE_PATTERN = /^\d{5}(?:\d{2})?$/; + private static DATE_PATTERN = /^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/; + private static YEAR_PATTERN = /^(19|20)\d{2}/; + private static NOTE_PATTERN = /^[\w\d\s.,&\n\r@\-;!']*$/; + private static EMAIL_PATTERN = /[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]+/; + private static ALPHA_PATTERN = /^[a-zA-Z]*$/; + private static NAME_PATTERN = /^[a-zA-Z '-]*$/; + private static NUMERIC_PATTERN = /^[0-9]*$/; + private static ALPHA_NUMERIC_PATTERN = /^[a-zA-Z0-9]*$/ + + public static readonly SPECIAL = { + group: null, + fields: [ + { + field: 'clientMessage', + validation: [Validators.required, Validators.minLength(5), + Validators.maxLength(3450), Validators.pattern(CommonGroups.NOTE_PATTERN)] + } + ] + }; + + public static readonly MEMBER_INFO = { + group: null, + fields: [ + { + field: 'limitRequested', + validation: [Validators.required], + validatorFn: [LoanLimitsService.minValueValidator, LoanLimitsService.maxValueValidator] + }, + {field: 'applicantType', validation: [Validators.required], validatorFn: [WizardValidators.CoApplicantValidator]}, + {field: 'livesInMaritalPropertyState', validation: [Validators.required]} + ] + }; + + public static readonly MEMBER_INFO_CC = { + group: null, + fields: [ + {field: 'applicantType', validation: [Validators.required], validatorFn: [WizardValidators.CoApplicantValidator]}, + {field: 'livesInMaritalPropertyState', validation: [Validators.required]} + ] + }; + + + public static readonly CO_APP_MEMBER_INFO = { + group: null, + fields: [ + {field: 'coApplicantLivesInMaritalPropertyState', validation: [], validatorFn: []}, + {field: 'confirmJointCredit', validation: [], validatorFn: []} + ] + }; + + public static readonly MEMBER_AGREEMENT_REQUIREMENT = { + group: null, + fields: [ + {field: 'agreementRequirement', validation: [Validators.required, Validators.requiredTrue], validatorFn: []} + ] + } + + public static readonly SUMMARY_ADDITIONAL_INFO = { + group: null, + fields: [ + {field: 'modFeeAccount', validation: []} + ] + } + + public static readonly SUMMARY_NOTE = { + group: null, + fields: [ + {field: 'note', validation: [Validators.maxLength(3500), Validators.pattern(CommonGroups.NOTE_PATTERN)]}, + {field: 'primaryContact', validation: [Validators.required, Validators.minLength(6)]}, + {field: 'promoCode', validation: [Validators.maxLength(30)]} + ] + }; + + public static readonly LOGIN = { + group: 'loginInformation', + fields: [ + {field: 'username', validation: [Validators.required, Validators.minLength(1), Validators.maxLength(30)]}, + {field: 'password', validation: [Validators.required, Validators.minLength(1), Validators.maxLength(30)]}, + ] + }; + + public static readonly MEMBER_VERIFICATION = { + group: 'memberInformation', + fields: [ + { + field: 'dob', + validation: [Validators.required, Validators.pattern(CommonGroups.DATE_PATTERN)] + }, + {field: 'ssn', validation: [Validators.required, Validators.pattern(CommonGroups.SSN_LAST_FOUR)]}, + ] + }; + + public static readonly IS_MARRIED = { + group: 'personalInformation', + fields: [ + {field: 'maritalStatus', validation: [Validators.required]} + ] + }; + + public static readonly IS_CO_APPLICANT_SPOUSE = { + group: 'personalInformation', + fields: [ + {field: 'coApplicantSpouse', validation: [Validators.required]} + ] + }; + + public static readonly PERSONAL_INFO = { + group: 'personalInformation', + groupValidator: WizardValidators.PersonalInfoValidator, + fields: [ + { + field: 'firstName', validation: [ + Validators.required, Validators.minLength(2), Validators.maxLength(60), Validators.pattern(CommonGroups.ALPHA_PATTERN) + ] + }, + {field: 'middleInitial', validation: [Validators.maxLength(1), Validators.pattern(CommonGroups.ALPHA_PATTERN)]}, + { + field: 'lastName', validation: [ + Validators.required, Validators.minLength(2), Validators.maxLength(60), Validators.pattern(CommonGroups.ALPHA_PATTERN) + ] + }, + {field: 'suffix', validation: []}, + { + field: 'dateOfBirth', + validation: [Validators.required, Validators.pattern(CommonGroups.DATE_PATTERN)] + }, + {field: 'socialSecurityNumber', validation: [Validators.required, Validators.pattern(CommonGroups.SSN_PATTERN)]}, + {field: 'primaryPhoneNumberType', validation: [Validators.required]}, + { + field: 'primaryPhoneNumber', + validation: [Validators.required, Validators.pattern(CommonGroups.PHONE_PATTERN)] + }, + { + field: 'secondaryPhoneNumberType', + validation: [WizardValidators.RequiredIfValidator('secondaryPhoneNumber')] + }, + { + field: 'secondaryPhoneNumber', + validation: [WizardValidators.FieldDependsOnMe('secondaryPhoneNumberType'), Validators.pattern(CommonGroups.PHONE_PATTERN)] + }, + { + field: 'email', validation: [ + Validators.required, Validators.pattern(CommonGroups.EMAIL_PATTERN), Validators.minLength(3), Validators.maxLength(180) + ] + }, + { + field: 'primaryApplicantEmail', + validation: [ Validators.pattern(CommonGroups.EMAIL_PATTERN) ] + }, + { + field: 'coApplicantEmail', + validation: [ Validators.pattern(CommonGroups.EMAIL_PATTERN), WizardValidators.ValueEqual("primaryApplicantEmail", "Primary Applicant Email Address") ] + }, + { + field: 'files', + validation: [WizardValidators.FileValidator] + }, + { + field: 'isUploading', + validation: [WizardValidators.MustBeFalse] + } + ] + }; + + public static readonly IS_FULL_INFO_SPOUSE = { + group: 'isFullInfoSpouse', + fields: [ + {field: 'isFullInfoSpouse', validation: [Validators.required]} + ] + }; + + public static readonly ADDRESS_INFO = { + group: 'addressInformation', + fields: [ + {field: 'addressLine1', validation: [Validators.required, Validators.minLength(5), Validators.maxLength(40)]}, + {field: 'addressLine2', validation: [Validators.minLength(2), Validators.maxLength(40)]}, + {field: 'zipcode', validation: [Validators.required, Validators.pattern(CommonGroups.ZIPCODE_PATTERN)]}, + {field: 'city', validation: [Validators.required, Validators.minLength(2), Validators.maxLength(50)]}, + {field: 'state', validation: [Validators.required], + validatorFn: [WizardValidators.RequireSpecificStateForAddressValidator, WizardValidators.UpdateCollateralMessage] + } + ] + }; + + public static readonly ADDRESS_VERIFY_STATUS_INFO = { + group: 'addressInformation', + fields: [ + {field: 'addressVerifyStatus', validation: [Validators.requiredTrue]} + ] + }; + + public static readonly COMPARE_ADDRESS_INFO = { + group: 'compareAddressInformation', + fields: [ + {field: 'addressSelection', validation: [Validators.required]}, + ] + }; + + public static readonly RESIDENCE_INFO = { + group: 'residenceInformation', + fields: [ + {field: 'residenceStatus', validation: [Validators.required]}, + {field: 'residenceTimeYears', validation: [Validators.required]}, + {field: 'residenceTimeMonths', validation: [Validators.required]} + ] + }; + + public static readonly PRIMARY_RESIDENCE_INFO = { + group: 'residenceInformation', + fields: [ + {field: 'residenceMonthlyPayment', validation: [Validators.required], validatorFn: [WizardValidators.maxCollateralValueValidator]}, + {field: 'isMailingAddressSame', validation: [Validators.required]} + ] + }; + + public static readonly IS_ADDRESS_SAME_INFO = { + group: 'isAddressSameInformation', + fields: [ + {field: 'isAddressSame', validation: [Validators.required], validatorFn: [WizardValidators.RequireWisconsinCollateralValidator]} + ] + }; + + public static readonly FINANCIAL_INFORMATION = { + group: 'financialInformation', + fields: [ + {field: 'hasEmploymentIncome0', validation: []}, + {field: 'hasEmploymentIncome1', validation: []}, + {field: 'hasEmploymentIncome2', validation: []}, + {field: 'hasOtherIncome0', validation: []}, + {field: 'hasOtherIncome1', validation: []}, + {field: 'hasOtherIncome2', validation: []}, + {field: 'isPayingChildSupport', validation: []} + ] + }; + + public static readonly EMPLOYMENT_INFO = { + group: 'employmentInformation', + fields: [ + {field: 'employerName', validation: [Validators.required, Validators.minLength(1), Validators.maxLength(60)]}, + {field: 'employmentType', validation: [Validators.required]}, + {field: 'jobTitle', validation: [Validators.required, Validators.minLength(1), Validators.maxLength(60)]}, + {field: 'schedule', validation: [Validators.required, WizardValidators.FieldDependsOnMe('salary')]}, + { + field: 'salary', validation: [Validators.required], + validatorFn: [IncomeLimitsService.minValueValidator, IncomeLimitsService.maxValueValidator] + }, + {field: 'employmentLengthYears', validation: [Validators.required]}, + {field: 'employmentLengthMonths', validation: [Validators.required]}, + {field: 'workPhoneNumber', validation: [Validators.pattern(CommonGroups.PHONE_PATTERN)]}, + {field: 'files', validation: [WizardValidators.FileValidator], validatorFn: []}, + {field: 'isUploading', validation: [WizardValidators.MustBeFalse]} + ] + }; + + public static readonly WEEKLY_HOURS_INFO = { + group: 'employmentInformation', + fields: [ + {field: 'weeklyHours', validation: [Validators.required, Validators.max(168), Validators.min(1)]} + ] + }; + + public static readonly INCOME_INFO = { + group: 'otherIncomeInformation', + fields: [ + {field: 'amount', validation: [Validators.required]}, + {field: 'incomeName', validation: [Validators.required, Validators.minLength(1), Validators.maxLength(60)]}, + {field: 'frequency', validation: []}, + {field: 'files', validation: [WizardValidators.FileValidator]}, + {field: 'isUploading', validation: [WizardValidators.MustBeFalse]} + ] + }; + + public static readonly ALIMONY_INFO = { + group: 'alimonyInformation', + fields: [ + {field: 'childSupportAmount', validation: [Validators.required]} + ] + }; + + public static readonly TATTLE_SPOUSE_PERSONAL_INFO = { + group: 'tattleSpousePersonalInformation', + fields: [ + {field: 'firstName', validation: [Validators.required, Validators.minLength(2), Validators.maxLength(60), Validators.pattern(CommonGroups.ALPHA_PATTERN)]}, + {field: 'middleInitial', validation: [Validators.maxLength(1)]}, + {field: 'lastName', validation: [Validators.required, Validators.minLength(2), Validators.maxLength(60), Validators.pattern(CommonGroups.ALPHA_PATTERN)]}, + // @TODO: Per Jeff's request this model is subject to change might be changed back. -ZB + // {field: 'socialSecurityNumber', validation: [Validators.required, Validators.pattern(CommonGroups.SSN_PATTERN)]}, + // {field: 'email', validation: [Validators.email]} + ] + }; + + public static readonly CC_DISCLOSURE_INFO = { + group: 'disclosureInformation', + fields: [ + {field: 'truthful', validation: [Validators.required, Validators.requiredTrue]}, + {field: 'contact', validation: [Validators.required]} + ] + }; + + public static readonly ETHNICITY_INFO = { + group: 'ethnicityInformation', + groupValidator: WizardValidators.RequireOneValidator, + fields: [ + {field: 'hispanicOrLatino', validation: []}, + {field: 'mexican', validation: []}, + {field: 'puertoRican', validation: []}, + {field: 'cuban', validation: []}, + {field: 'otherHispanicLatino', validation: []}, + {field: 'otherHispanicLatinoText', validation: []}, + {field: 'notHispanicOrLatino', validation: []}, + {field: 'notProvided', validation: []} + ] + }; + + public static readonly GENDER_INFO = { + group: 'genderInformation', + groupValidator: WizardValidators.RequireOneValidator, + fields: [ + {field: 'female', validation: []}, + {field: 'male', validation: []}, + // The code below would make it so that the applciant cannot select both "Male" and "Female". + // Code remains in comments due to suggestion on ticket #1116. + // {field: 'female', validation: [], validatorFn: [WizardValidators.HmdaGenderValidator]}, + // {field: 'male', validation: [WizardValidators.FieldDependsOnMe('female')], validatorFn: [WizardValidators.HmdaGenderValidator]}, + {field: 'notProvided', validation: []} + ] + }; + + public static readonly RACE_INFO = { + group: 'raceInformation', + groupValidator: WizardValidators.RequireOneValidator, + fields: [ + {field: 'americanIndianAlaskaNative', validation: []}, + {field: 'americanIndianAlaskaNativeText', validation: []}, + {field: 'blackOrAfricanAmerican', validation: []}, + {field: 'asian', validation: []}, + {field: 'asianIndian', validation: []}, + {field: 'chinese', validation: []}, + {field: 'filipino', validation: []}, + {field: 'japanese', validation: []}, + {field: 'korean', validation: []}, + {field: 'vietnamese', validation: []}, + {field: 'otherAsian', validation: []}, + {field: 'otherAsianText', validation: []}, + {field: 'hawaiian', validation: []}, + {field: 'nativeHawaiian', validation: []}, + {field: 'guamanianChamorro', validation: []}, + {field: 'samoan', validation: []}, + {field: 'otherPacificIslander', validation: []}, + {field: 'otherPacificIslanderText', validation: []}, + {field: 'white', validation: []}, + {field: 'notProvided', validation: []} + ] + }; + + public static readonly MEMBER_INFO_PURPOSE_OF_LOAN = { + group: 'purposeOfLoan', + fields: [ + {field: 'purposeOfLoan', validation: [Validators.required]} + ] + }; + + public static readonly MEMBER_INFO_PURPOSE_OF_LOAN_SIGNATURE = { + group: 'purposeOfLoanSignature', + fields: [ + {field: 'purposeOfLoanSignature', validation: [Validators.required, Validators.maxLength(100)]} + ] + }; + + public static readonly MEMBER_INFO_TERM_OF_LOAN = { + group: 'termOfLoan', + fields: [ + {field: 'termOfLoan', validation: [Validators.required]} + ] + }; + + public static readonly COLLATERAL_INFO = { + group: 'collateralInformation', + fields: [ + {field: 'estimatedValue', validation: [], validatorFn: [WizardValidators.maxCollateralValueValidator, WizardValidators.HelocRateModAppValidator]}, + {field: 'propertyType', validation: [], validatorFn: [WizardValidators.HelocRateModAppValidator]}, + {field: 'isBeingImproved', validation: [Validators.required]}, + {field: 'isListedForSale', validation: [], validatorFn: [WizardValidators.ListedForSaleValidator]}, + {field: 'occupancyType', validation: [Validators.required]}, + // @TODO: Another case of not knowing how to do this validation properly ( task 595 - Bart ) + { + field: 'onRentedLot', + validation: [WizardValidators.FieldsDependsOnMe(['rentPaid', 'annualPropertyTax'])], + validatorFn: [WizardValidators.RentedLotValidator] + }, + {field: 'annualPropertyTax', validation: [], validatorFn: [WizardValidators.AnnualPropertyTaxValidator]}, + {field: 'rentPaid', validation: [], validatorFn: [WizardValidators.RentPaidValidator]}, + {field: 'files', validation: [WizardValidators.FileValidator]}, + {field: 'firstMortgageBalanceFiles', validation: [WizardValidators.FileValidator]}, + {field: 'isUploadingFirstMortgageBalanceFiles', validation: [WizardValidators.MustBeFalse]}, + {field: 'isUploading', validation: [WizardValidators.MustBeFalse]} + ] + }; + + public static readonly LIEN_POSITION_INFO = { + group: 'collateralInformation', + fields: [ + {field: 'hasFirstMortgage', validation: [], validatorFn: [WizardValidators.LienPositionValidator]}, + {field: 'hasSecondMortgage', validation: [], validatorFn: [WizardValidators.LienPositionValidator]}, + {field: 'payoffFirstMortgage', validation: []}, + {field: 'payoffSecondMortgage', validation: []} + ] + }; + + public static readonly LIEN_POSITION_INFO_FIRST_MORTGAGE_PAYOFF = { + group: 'collateralInformation', + fields: [ + {field: 'payoffFirstMortgage', validation: []} + ] + }; + + public static readonly LIEN_POSITION_INFO_SECOND_MORTGAGE_PAYOFF = { + group: 'collateralInformation', + fields: [ + {field: 'payoffSecondMortgage', validation: []} + ] + }; + + public static readonly VEHICLE_COLLATERAL_INFO = { + group: 'vehicleInformation', + fields: [ + {field: 'value', validation: [Validators.required], validatorFn: [WizardValidators.maxCollateralValueValidator]}, + {field: 'type', validation: [Validators.required]}, + {field: 'condition', validation: [Validators.required]}, + { + field: 'year', + validation: [Validators.required, Validators.pattern(CommonGroups.YEAR_PATTERN), + Validators.max(CreditApplicationValues.VEHICLE_CUT_OFF_YEAR_UPPER), + Validators.min(CreditApplicationValues.VEHICLE_CUT_OFF_YEAR_LOWER)] + }, + {field: 'make', validation: [Validators.required, Validators.maxLength(100)]}, + {field: 'model', validation: [Validators.required, Validators.maxLength(100)]}, + {field: 'mileage', validation: [Validators.required, Validators.minLength(1)]}, + {field: 'term', validation: [Validators.required]}, + {field: 'loanPurpose', validation: [Validators.required]}, + {field: 'vin', validation: [Validators.maxLength(20), Validators.pattern(CommonGroups.ALPHA_NUMERIC_PATTERN)]}, + {field: 'licensePlateNumber', validation: [Validators.maxLength(8), Validators.pattern(CommonGroups.ALPHA_NUMERIC_PATTERN)]}, + {field: 'stateTitled', validation: [Validators.maxLength(120), Validators.required], validatorFn: [WizardValidators.VehicleDefaultValidator]}, + {field: 'countyTitled', validation: [Validators.maxLength(20), Validators.required], validatorFn: [WizardValidators.VehicleDefaultValidator]}, + {field: 'municipalityType', validation: []}, + {field: 'municipalityWhereKept', validation: [Validators.maxLength(50)]} + ] + }; + + public static readonly VEHICLE_COLLATERAL_INFO_PARTIAL = { + group: 'vehicleInformation', + fields: [ + {field: 'value', validation: [Validators.required], validatorFn: [WizardValidators.maxCollateralValueValidator]}, + { + field: 'year', + validation: [Validators.required, Validators.pattern(CommonGroups.YEAR_PATTERN), Validators.max(3000), Validators.min(1900)] + }, + {field: 'type', validation: [Validators.required]}, + {field: 'make', validation: [Validators.required, Validators.maxLength(100)]}, + {field: 'model', validation: [Validators.required, Validators.maxLength(100)]}, + {field: 'term', validation: [Validators.required]}, + {field: 'loanPurpose', validation: [Validators.required]}, + {field: 'mileage', validation: []}, + {field: 'vin', validation: [Validators.pattern(CommonGroups.ALPHA_NUMERIC_PATTERN)], validatorFn: [WizardValidators.VINMaxLength]}, + {field: 'municipalityType', validation: []}, + {field: 'municipalityWhereKept', validation: [Validators.maxLength(50)]} + ] + }; + + public static readonly VEHICLE_SELLER_INFO = { + group: 'vehicleInformation', + groupValidator: WizardValidators.BrandedTitleValidator, + fields: [ + {field: 'transferring', validation: [Validators.maxLength(120)], validatorFn: [WizardValidators.RequireTransferring]}, + {field: 'licensePlateNumber', validation: [Validators.maxLength(8), Validators.pattern(CommonGroups.ALPHA_NUMERIC_PATTERN)]}, + {field: 'seller', validation: [Validators.maxLength(120)], validatorFn: [WizardValidators.RequireSeller]}, + {field: 'dealerName', validation: [Validators.maxLength(120)]}, + {field: 'dealerPhone', validation: [Validators.pattern(CommonGroups.PHONE_PATTERN)]}, + {field: 'purchaseContractFiles', validation: [WizardValidators.FileValidator]}, + {field: 'purchaseContractUploading', validation: [WizardValidators.MustBeFalse]}, + {field: 'titleBranded', validation: [Validators.maxLength(120)]}, + {field: 'lien', validation: [Validators.maxLength(120)]} + ] + }; + + public static readonly VEHICLE_BOAT_INFO = { + group: 'vehicleInformation', + fields: [ + {field: 'boatLength', validation: [Validators.maxLength(30), WizardValidators.NumberMaxLengthValidator(15)], validatorFn: []}, + {field: 'boatSameAddress', validation: [Validators.required]}, + {field: 'trailerYear', validation: [Validators.pattern(CommonGroups.YEAR_PATTERN)]}, + {field: 'trailerMake', validation: [Validators.maxLength(60)]}, + {field: 'trailerType', validation: []}, + {field: 'motorYear', validation: [Validators.pattern(CommonGroups.YEAR_PATTERN)]}, + {field: 'motorMake', validation: [Validators.maxLength(60)]}, + {field: 'motorModel', validation: [Validators.maxLength(200)]}, + {field: 'motorType', validation: []}, + {field: 'dnrRegistrationCardFiles', validation: [WizardValidators.FileValidator]}, + {field: 'boatTitleFiles', validation: [WizardValidators.FileValidator]}, + {field: 'trailerTitleFiles', validation: [WizardValidators.FileValidator]}, + {field: 'dnrRegistrationCardUploading', validation: [WizardValidators.MustBeFalse]}, + {field: 'boatTitleUploading', validation: [WizardValidators.MustBeFalse]}, + {field: 'trailerTitleUploading', validation: [WizardValidators.MustBeFalse]} + ] + } + + public static readonly DEALERSHIP_PORTAL = { + group: 'dealershipPortal', + fields: [ + {field: 'dealershipCode', validation: [Validators.required, Validators.maxLength(60)], validatorFn: []}, + {field: 'dealershipPassword', validation: [Validators.required, Validators.maxLength(30)]}, + {field: 'applicationId', validation: [Validators.required, Validators.pattern(CommonGroups.NUMERIC_PATTERN)]}, + {field: 'applicantLastName', validation: [Validators.required, Validators.maxLength(250)]}, + {field: 'applicationFiles', validation: [WizardValidators.FileValidator]}, + {field: 'applicationFilesUploading', validation: [WizardValidators.MustBeFalse]}, + ] + } + static memberService: any; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.html new file mode 100644 index 0000000..864597a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.html @@ -0,0 +1,25 @@ +
+ +
+ + +
+ + + +
+
+ +
+ + +
+ + +
+
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.scss new file mode 100644 index 0000000..2c5065e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.scss @@ -0,0 +1,2 @@ +.rowContainer { height: 100px; display:flex; align-items: center;} +.rowItem { width: 30px; height: 30px; margin-right: 5px; } diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.spec.ts new file mode 100644 index 0000000..c543945 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.spec.ts @@ -0,0 +1,57 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { CreditCardDisclosureFormComponent } from './credit-card-disclosure-form.component'; +import {TranslateModule} from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.CC_DISCLOSURE_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('CreditCardDisclosureBoxesComponent', () => { + let component: CreditCardDisclosureFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + CreditCardDisclosureFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(CreditCardDisclosureFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.ts new file mode 100644 index 0000000..b98c13e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure-form.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-credit-card-disclosure-form', + templateUrl: './credit-card-disclosure-form.component.html' +}) +export class CreditCardDisclosureFormComponent { + + @Input() + public group: FormGroup; + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure.ts new file mode 100644 index 0000000..367797c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure.ts @@ -0,0 +1,5 @@ +export interface DisclosureInformation { + truthful: boolean; + contact: boolean; + date?: Date; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-agreement.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-agreement.html new file mode 100644 index 0000000..d1247e5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-agreement.html @@ -0,0 +1,43 @@ +DISCLOSURE.ELECTRONIC.PART1 + + + + + + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-form.component.spec.ts new file mode 100644 index 0000000..142bcae --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-form.component.spec.ts @@ -0,0 +1,41 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { ElectronicDisclosuresFormComponent } from './electronic-disclosures-form.component'; +import { configureTestSuite } from 'ng-bullet'; + +describe('ElectronicDisclosuresFormComponent', () => { + let component: ElectronicDisclosuresFormComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + ElectronicDisclosuresFormComponent + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ElectronicDisclosuresFormComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-form.component.ts new file mode 100644 index 0000000..146a0d9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/disclosures/electronic-disclosures-form.component.ts @@ -0,0 +1,19 @@ +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { WindowService } from '../../../shared/window.service'; + +@Component({ + selector: 'lw-electronic-disclosure', + templateUrl: './electronic-disclosures-agreement.html' +}) +export class ElectronicDisclosuresFormComponent { + constructor(private modalService: NgbModal, private windowService: WindowService) {} + @ViewChild('disclosures') modalContent: ElementRef; + open() { + this.modalService.open(this.modalContent, {size: 'lg'}); + } + print() { + this.windowService.showPrintWindowForId('electronicDisclosuresText'); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.html new file mode 100644 index 0000000..166baea --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.html @@ -0,0 +1,19 @@ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.spec.ts new file mode 100644 index 0000000..fb2b414 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.spec.ts @@ -0,0 +1,59 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TranslateModule } from '@ngx-translate/core'; +import { AlimonyInfoFormComponent } from './alimony-info-form.component'; +import { configureTestSuite } from 'ng-bullet'; + +const createAlimonyFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.ALIMONY_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('AlimonyInfoFormComponent', () => { + let component: AlimonyInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let alimonyGroup: FormGroup; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + AlimonyInfoFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + alimonyGroup = createAlimonyFormGroup(formBuilder); + fixture = TestBed.createComponent(AlimonyInfoFormComponent); + component = fixture.componentInstance; + component.group = alimonyGroup; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.ts new file mode 100644 index 0000000..1605a28 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-info-form.component.ts @@ -0,0 +1,17 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MaskingService } from '../../../shared/masking/masking.service'; + +@Component({ + selector: 'lw-alimony-info-form', + templateUrl: './alimony-info-form.component.html' +}) +export class AlimonyInfoFormComponent { + + public numberMask = MaskingService.numberMask; + + @Input() + public group: FormGroup; + + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-information.ts new file mode 100644 index 0000000..24a0948 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/alimony-information.ts @@ -0,0 +1,3 @@ +export interface AlimonyInformation { + childSupportAmount: number; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.html new file mode 100644 index 0000000..4f74424 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.html @@ -0,0 +1,150 @@ +
+
+
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ + FORMS.EMPLOYMENT.SALARY_HINT +
+
+
+ +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+
+

FORMS.EMPLOYMENT.LENGTH_OF_EMPLOYMENT

+
+
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.spec.ts new file mode 100644 index 0000000..b580089 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.spec.ts @@ -0,0 +1,83 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { EmploymentInfoFormComponent } from './employment-info-form.component'; +import { EmploymentInformation } from './employment-information'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TranslateModule } from '@ngx-translate/core'; +import { TcpaComponent } from '../../../shared/legal-message/tcpa.component'; +import { configureTestSuite } from 'ng-bullet'; +import { FileUploadComponent } from 'src/app/shared/file-upload/file-upload.component'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.EMPLOYMENT_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('EmploymentInfoFormComponent', () => { + let component: EmploymentInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: EmploymentInformation; + let status: string; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + EmploymentInfoFormComponent, + TcpaComponent, + FileUploadComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(EmploymentInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + fixture.detectChanges(); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({ 'weeklyHours': 40, 'employerName': 'NVISIA', '401K': 'vested' }); + expect(changes['401K']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + component.group.patchValue({ 'weeklyHours': 40, 'employerName': 'NVISIA' }); + expect(status).toBe('INVALID'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.ts new file mode 100644 index 0000000..6994b9e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-info-form.component.ts @@ -0,0 +1,88 @@ +import { Component, Input, OnInit, OnDestroy, OnChanges, EventEmitter, Output } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MaskingService } from '../../../shared/masking/masking.service'; +import { Subject, Subscription } from 'rxjs'; +import { SelectOptions } from '../../../shared/options/select-options'; + +@Component({ + selector: 'lw-employment-info-form', + templateUrl: './employment-info-form.component.html' +}) +export class EmploymentInfoFormComponent implements OnInit, OnDestroy { + + private groupSubscription: Subscription; + public phoneMask = MaskingService.phoneMask; + public numberMask = MaskingService.numberMask; + public numberMaskDecimal = MaskingService.numberMaskWithDecimal; + public noSpecialMask = MaskingService.noSpecial; + + public localState = { + isHourly: false + }; + + // From Swagger: NoneSelected, Contract, FullTime, PartTime, SelfEmployed, Temporary + public employmentTypeOptions = SelectOptions.employmentTypeOptions; + public employmentScheduleOptions = SelectOptions.employmentScheduleOptions; + public employmentYearOptions = SelectOptions.yearOptions; + public employmentMonthOptions = SelectOptions.monthOptions; + + @Input() + public group: FormGroup; + + @Input() + public loanId: string; + + @Input() + public idSuffix = 0; + + @Input() + public borrowerType: string; + + @Input() + public showProofOfIncomeUploadForm: boolean; + + @Input() + public removeEmploymentIncomeFiles: Subject; + + @Output() + public removedAllFiles: EventEmitter; + + constructor() { + this.removedAllFiles = new EventEmitter(); + } + + public ngOnInit() { + if (this.group) { + if (!this.showProofOfIncomeUploadForm) { + this.group.patchValue({ 'files': [] }); + } + this.updateGroupLocalState(); + this.groupSubscription = this.group.valueChanges.subscribe((nextValues) => { + this.updateGroupLocalState(); + }); + } + } + + public ngOnDestroy() { + if (this.groupSubscription) { + this.groupSubscription.unsubscribe(); + } + } + + public updateGroupLocalState(): void { + this.localState.isHourly = !!(this.group && this.group.get('weeklyHours')); + } + + public handleFiles(files: any[]): void { + this.group.patchValue({ 'files': files }); + } + + public removedEmploymentIncomeFiles(removedAllFiles: boolean): void { + this.removedAllFiles.emit(removedAllFiles); + } + + public handleUploadingFiles(isUploading: boolean): void { + this.group.patchValue({ 'isUploading': isUploading }); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-information.ts new file mode 100644 index 0000000..1394ffd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/employment-information.ts @@ -0,0 +1,15 @@ +import { IncomeInformation } from './income-information'; + +export interface EmploymentInformation { + employerName: string; + employmentType: string; + jobTitle: string; + salary: number; + schedule: string; + employmentLengthYears: number; + employmentLengthMonths: number; + weeklyHours?: number; + workPhoneNumber?: string; + isUploading?: string; + files?: any[]; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial-information.ts new file mode 100644 index 0000000..c5f2263 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial-information.ts @@ -0,0 +1,5 @@ +export interface FinancialInformation { + hasEmploymentIncome?: boolean; + hasOtherIncome?: boolean; + isPayingChildSupport?: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.html new file mode 100644 index 0000000..678af0d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.html @@ -0,0 +1,10 @@ +
+
+
+
+ + +
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.spec.ts new file mode 100644 index 0000000..833516d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.spec.ts @@ -0,0 +1,60 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TranslateModule } from '@ngx-translate/core'; +import { AlimonyFlagFormComponent } from './alimony-flag-form.component'; +import { CommonGroups } from '../../common-groups'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.FINANCIAL_INFORMATION.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('AlimonyFlagFormComponent', () => { + let component: AlimonyFlagFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + AlimonyFlagFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(AlimonyFlagFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + fixture.detectChanges(); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.ts new file mode 100644 index 0000000..bba44e9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/alimony-flag-form.component.ts @@ -0,0 +1,15 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-alimony-flag-form', + templateUrl: './alimony-flag-form.component.html' +}) +export class AlimonyFlagFormComponent { + + @Input() + public group: FormGroup; + + constructor() { } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.html new file mode 100644 index 0000000..63d78f4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.html @@ -0,0 +1,10 @@ +
+
+
+
+ + +
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.spec.ts new file mode 100644 index 0000000..e061212 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.spec.ts @@ -0,0 +1,60 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonGroups } from '../../common-groups'; +import { EmploymentFlagFormComponent } from './employment-flag-form.component'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.FINANCIAL_INFORMATION.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('EmploymentFlagFormComponent', () => { + let component: EmploymentFlagFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + EmploymentFlagFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(EmploymentFlagFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + fixture.detectChanges(); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.ts new file mode 100644 index 0000000..ca68e73 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/employment-flag-form.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-employment-flag-form', + templateUrl: './employment-flag-form.component.html' +}) +export class EmploymentFlagFormComponent { + + @Input() + public group: FormGroup; + + @Input() + public idSuffix = 0; + + constructor() { + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.html new file mode 100644 index 0000000..da67f8f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.html @@ -0,0 +1,10 @@ +
+
+
+
+ + +
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.spec.ts new file mode 100644 index 0000000..dc88a3f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.spec.ts @@ -0,0 +1,60 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonGroups } from '../../common-groups'; +import { OtherIncomeFlagFormComponent } from './other-income-flag-form.component'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.FINANCIAL_INFORMATION.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('OtherIncomeFlagFormComponent', () => { + let component: OtherIncomeFlagFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + OtherIncomeFlagFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(OtherIncomeFlagFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + fixture.detectChanges(); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.ts new file mode 100644 index 0000000..f501e22 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/financial/other-income-flag-form.component.ts @@ -0,0 +1,21 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-other-income-flag-form', + templateUrl: './other-income-flag-form.component.html' +}) +export class OtherIncomeFlagFormComponent { + + @Input() + public group: FormGroup; + + @Input() + public idSuffix = 0; + + @Input() + public loanId: string; + + constructor() { } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/income-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/income-information.ts new file mode 100644 index 0000000..f020f75 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/income-information.ts @@ -0,0 +1,7 @@ +export interface IncomeInformation { + incomeName: string; + frequency?: string; + amount: number; + isUploading?: string; + files?: any[]; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.html new file mode 100644 index 0000000..29df553 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.html @@ -0,0 +1,51 @@ +
+
+
+ +
+
+
+ +
+ +
+ +
+ + +
+ +
+ +
+
+
+ +
+ +
+ +
+ + +
+
+ + +
+ + +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.spec.ts new file mode 100644 index 0000000..ab2a2f8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.spec.ts @@ -0,0 +1,61 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TranslateModule } from '@ngx-translate/core'; +import { OtherIncomeInfoFormComponent } from './other-income-info-form.component'; +import { configureTestSuite } from 'ng-bullet'; +import { FileUploadComponent } from 'src/app/shared/file-upload/file-upload.component'; + +const createIncomeFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.INCOME_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('OtherIncomeInfoFormComponent', () => { + let component: OtherIncomeInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let incomeGroup: FormGroup; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + OtherIncomeInfoFormComponent, + FileUploadComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + incomeGroup = createIncomeFormGroup(formBuilder); + fixture = TestBed.createComponent(OtherIncomeInfoFormComponent); + component = fixture.componentInstance; + component.group = incomeGroup; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.ts new file mode 100644 index 0000000..ea1ae25 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/employment/other-income-info-form.component.ts @@ -0,0 +1,59 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { Subject } from 'rxjs'; +import { MaskingService } from '../../../shared/masking/masking.service'; + +@Component({ + selector: 'lw-other-income-info-form', + templateUrl: './other-income-info-form.component.html' +}) +export class OtherIncomeInfoFormComponent { + + public numberMask = MaskingService.numberMask; + + @Input() + public loanId: string; + + @Input() + public idSuffix = 0; + + @Input() + public group: FormGroup; + + @Input() + public borrowerType: string; + + @Input() + public showProofOfIncomeUploadForm: boolean; + + @Input() + public removeOtherIncomeFiles: Subject>; + + @Output() + public removedAllFiles: EventEmitter; + + constructor() { + this.removedAllFiles = new EventEmitter(); + } + + public ngOnInit() { + if (this.group) { + if (!this.showProofOfIncomeUploadForm) { + this.group.patchValue({ 'files': [] }); + } + } + } + + public removedOtherIncomeFiles(removedAllFiles: boolean): void { + this.removedAllFiles.emit(removedAllFiles); + } + + public handleFiles(files: any[]): void { + this.group.patchValue({ 'files': files }); + } + + public handleUploadingFiles(isUploading: boolean): void { + this.group.patchValue({ 'isUploading': isUploading }); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.html new file mode 100644 index 0000000..07f5c44 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.html @@ -0,0 +1,182 @@ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ + + +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ +
+ + +
+ +
+
+ +
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+ +
+
+ + +
+
+
+ +
+ +
+ +
+
+ +
+
+ + +
+ +
+
+ +
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.spec.ts new file mode 100644 index 0000000..8b66b2a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.spec.ts @@ -0,0 +1,137 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { CollateralInfoFormComponent } from './collateral-info-form.component'; +import { CollateralInformation } from './collateral-information'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { CollateralInfoFormService } from './collateral-info-form.service'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { CreditApplicationValues } from '../../../shared/credit-application-values'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const memberGroup = formBuilder.group({}); + + CommonGroups.COLLATERAL_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; +}; + +describe('CollateralInfoFormComponent', () => { + let component: CollateralInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: CollateralInformation; + let status: string; + let service: CollateralInfoFormService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + CollateralInfoFormComponent + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [ NO_ERRORS_SCHEMA ] + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(CollateralInfoFormComponent); + service = TestBed.get(CollateralInfoFormService); + component = fixture.componentInstance; + component.group = formGroup; + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit changes', () => { + component.group.patchValue({'isListedForSale': CreditApplicationValues.CHOICE_YES}); + expect(changes.isListedForSale).toBe(CreditApplicationValues.CHOICE_YES); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({'isMailingAddressSame': CreditApplicationValues.CHOICE_NO}); + expect(changes['isMailingAddressSame']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + expect(status).toBe('INVALID'); + }); + + it('should emit VALID for complete form', () => { + component.group.patchValue({ + 'estimatedValue': 5446, + 'propertyType': 1, + 'hasFirstMortgage' : CreditApplicationValues.CHOICE_YES, + 'hasSecondMortgage' : CreditApplicationValues.CHOICE_YES, + 'payoffFirstMortgage' : false, + 'payoffSecondMortgage': false, + 'isBeingImproved': CreditApplicationValues.CHOICE_YES, + 'isListedForSale': CreditApplicationValues.CHOICE_NO, + 'occupancyType': 0, + 'onRentedLot': false + }); + expect(status).toBe('VALID'); + }); + + it('should leave isListedForSale true if !isMfgHome and !isHelocRateMod', () => { + component.group.patchValue({ 'isListedForSale': 'true' }) + component.isMfgHome = false; + component.isHelocRateMod = false; + + component.updateIsListedForSale(); + + expect(component.group.get('isListedForSale').value).toBe('true'); + }); + + it('should set isListedForSale to false if isMfgHome', () => { + component.group.patchValue({ 'isListedForSale': 'true' }) + component.isMfgHome = true; + component.isHelocRateMod = false; + + component.updateIsListedForSale(); + + expect(component.group.get('isListedForSale').value).toBe('false'); + }); + + it('should set isListedForSale to false if isHelocRateMod', () => { + component.group.patchValue({ 'isListedForSale': 'true' }) + component.isMfgHome = false; + component.isHelocRateMod = true; + + component.updateIsListedForSale(); + + expect(component.group.get('isListedForSale').value).toBe('false'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.ts new file mode 100644 index 0000000..1abe73a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.component.ts @@ -0,0 +1,125 @@ +import { Component, Input, OnInit, OnDestroy, EventEmitter, Output } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MaskingService } from '../../../shared/masking/masking.service'; +import {CollateralInfoFormService} from './collateral-info-form.service'; +import { Subject, Subscription } from 'rxjs'; +import { CreditApplicationValues } from 'src/app/shared/credit-application-values'; +import { SelectOptions } from '../../../shared/options/select-options'; +import { ThrowStmt } from '@angular/compiler'; + +@Component({ + selector: 'lw-collateral-info-form', + templateUrl: './collateral-info-form.component.html' +}) +export class CollateralInfoFormComponent implements OnInit, OnDestroy { + + public occupancyTypes = SelectOptions.occupancyTypes; + public localState = { + showLienPosition: false, + payoffFirstMortgage: false, + payoffSecondMortgage: false, + showPayoffFirstMortgageStatementUploadForm: false + }; + + @Input() + public group: FormGroup; + + @Input() + public idPrefix: string; + + @Input() + public loanType: string; + + @Input() + public addressSameAsResidence: boolean; + + @Input() + public isHelocRateMod: boolean; + + @Input() + public isMfgHome: boolean; + + @Input() + public loanId: string; + + @Input() + public fileUploadValues: any; + + public selectedTypes; + + public numberMask = MaskingService.numberMask; + + private subscription: Subscription; + + public removeStatementFiles: Subject; + + @Output() + public removedStatementFiles: EventEmitter; + + constructor(private collateralInfoService: CollateralInfoFormService) { + this.removeStatementFiles = new Subject(); + this.removedStatementFiles = new EventEmitter(); + } + + public ngOnInit() { + this.selectedTypes = this.collateralInfoService.getPropertyTypes(); + + this.updateIsListedForSale(); + + this.updateLocalState(this.group.value); + this.subscription = this.group.valueChanges.subscribe(() => { + this.updateLocalState(this.group.value); + }); + } + + public ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public updateIsListedForSale() { + if(this.isMfgHome || this.isHelocRateMod) { + this.group.patchValue({isListedForSale: 'false'}); + } + } + + public updateLocalState(modelValue): void { + + this.localState.showLienPosition = this.collateralInfoService.showLienPosition(); + this.localState.showPayoffFirstMortgageStatementUploadForm = this.collateralInfoService.showPayoffFirstMortgageStatementUploadForm(); + + if (this.localState.showLienPosition) { + if(this.localState.showPayoffFirstMortgageStatementUploadForm && modelValue.hasFirstMortgage === CreditApplicationValues.CHOICE_NO) { + this.removeStatementFiles.next(); + } else { + this.localState.payoffFirstMortgage = (modelValue.hasFirstMortgage === CreditApplicationValues.CHOICE_YES); + this.localState.payoffSecondMortgage = (modelValue.hasSecondMortgage === CreditApplicationValues.CHOICE_YES); + } + } + } + + public handleFiles(files: any[]): void { + this.group.patchValue({ 'files': files }); + } + + public handleFirstMortgageBalanceFiles(files: any[]): void { + this.group.patchValue({ 'firstMortgageBalanceFiles': files }); + } + + public handleUploadingFirstMortgageBalanceFiles(isUploading: boolean): void { + this.group.patchValue({ 'isUploadingFirstMortgageBalanceFiles': isUploading }); + } + + public handleUploadingFiles(isUploading: boolean): void { + this.group.patchValue({ 'isUploading': isUploading }); + } + + public handleRemovedStatementFiles(removedAllFiles: boolean): void { + if(removedAllFiles) { + this.localState.payoffFirstMortgage = (this.group.value.hasFirstMortgage === CreditApplicationValues.CHOICE_YES); + this.localState.payoffSecondMortgage = (this.group.value.hasSecondMortgage === CreditApplicationValues.CHOICE_YES); + } + } +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.service.spec.ts new file mode 100644 index 0000000..240b836 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.service.spec.ts @@ -0,0 +1,66 @@ +import { ApplicationWizardService } from '../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import {CollateralInfoFormService} from './collateral-info-form.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Collateral Info Form Service', () => { + + let service: CollateralInfoFormService; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(CollateralInfoFormService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[5].id); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should call function for real estate options', () => { + spyOn(service, 'getPropertyTypes').and.callThrough(); + service.getPropertyTypes(); + expect(service.getPropertyTypes).toHaveBeenCalled(); + }); + + it('should return term options when given a correct loanId', () => { + applicationWizardService.setLoanType(LoanTypeSet[7].id); + spyOn(service, 'getPropertyTypes').and.callThrough(); + + // Only options are Real Estate or Manufactured Home.. Manufactured home was removed in 2023 + expect(service.getPropertyTypes()).toEqual(service.realEstateOptions); + expect(service.getPropertyTypes().length).toBeGreaterThan(1); + }); + + it('should return empty term options when given incorrect loanId', () => { + applicationWizardService.setLoanType(LoanTypeSet[0].id); + spyOn(service, 'getPropertyTypes').and.callThrough(); + expect(service.getPropertyTypes()).toEqual(service.realEstateOptions); + }); + + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.service.ts new file mode 100644 index 0000000..07dce79 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-info-form.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { SelectOptions } from 'src/app/shared/options/select-options'; + +@Injectable({ + providedIn: 'root' +}) +export class CollateralInfoFormService { + + constructor(private applicationWizardService: ApplicationWizardService) {} + + public realEstateOptions = SelectOptions.realEstateOptions; + public manufacturedHomeRealEstateOptions = SelectOptions.manufacturedHomeRealEstateOptions; + + private loanIdToTerms = { + 'mhl' : this.manufacturedHomeRealEstateOptions + }; + + public getPropertyTypes() { + const loanId = this.applicationWizardService.getFieldValue('loanType').id; + let types = this.realEstateOptions; + if (this.loanIdToTerms[loanId]) { + types = this.loanIdToTerms[loanId]; + } + return types; + } + + public showLienPosition(): boolean { + return this.applicationWizardService.isLienPositionInfoRequired(); + } + + public showPayoffFirstMortgageStatementUploadForm(): boolean { + return this.applicationWizardService.showPayoffFirstMortgageStatementUploadForm(); + } + +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-information.ts new file mode 100644 index 0000000..2b1bc60 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/collateral-information.ts @@ -0,0 +1,14 @@ +export interface CollateralInformation { + estimatedValue: number; + propertyType: string; + isBeingImproved: string; + isListedForSale: string; + occupancyType: string; + onRentedLot?: string; + annualPropertyTax?: string; + rentPaid?: string; + files?: any[]; + firstMortgageBalanceFiles?: any[]; + isUploading?: boolean; + isUploadingFirstMortgageBalanceFiles?: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/equity-member-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/equity-member-information.ts new file mode 100644 index 0000000..636e1b1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/equity-member-information.ts @@ -0,0 +1,10 @@ +import { LoanPurpose } from 'src/app/shared/loan-type/loan-purpose'; + +/* + * @TODO Merge this with membership-information.ts and update functions using them or 'any'. + */ +export interface EquityMemberInformation { + purposeOfLoan: LoanPurpose; + termOfLoan: string; + clientMessage?: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.html new file mode 100644 index 0000000..7daede7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.html @@ -0,0 +1,49 @@ +
+ +
+
+ +
+
+ + +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.spec.ts new file mode 100644 index 0000000..cfc1515 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.spec.ts @@ -0,0 +1,85 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { DataTableService } from '../../../shared/data-table.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { IsRentedLotInfoFormComponent } from './is-rented-lot-info-form.component'; +import { CollateralInformation } from './collateral-information'; +import {TextMaskModule} from 'angular2-text-mask'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const memberGroup = formBuilder.group({}); + + CommonGroups.COLLATERAL_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; +}; + +describe('IsRentedLotInfoFormComponent', () => { + let component: IsRentedLotInfoFormComponent; + let fixture: ComponentFixture; + let service: DataTableService; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: CollateralInformation; + let status: string; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + IsRentedLotInfoFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(IsRentedLotInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + service = TestBed.get(DataTableService); + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit changes', () => { + component.group.patchValue({'onRentedLot': 'yes'}); + expect(changes.onRentedLot).toBe('yes'); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({'isMailingAddressSame': false}); + expect(changes['isMailingAddressSame']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + expect(status).toBe('INVALID'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.ts new file mode 100644 index 0000000..59194cd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/equity/is-rented-lot-info-form.component.ts @@ -0,0 +1,44 @@ +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MaskingService } from '../../../shared/masking/masking.service'; +import { Subscription } from 'rxjs'; +import { CreditApplicationValues } from '../../../shared/credit-application-values'; + +@Component({ + selector: 'lw-is-rented-lot-info-form', + templateUrl: './is-rented-lot-info-form.component.html' +}) +export class IsRentedLotInfoFormComponent implements OnInit, OnDestroy { + + public localState = { + isRentedLot: false, + showAnnualPropertyTax: false + }; + + @Input() + public group: FormGroup; + + @Input() + public idPrefix: string; + + public numberMaskDecimal = MaskingService.numberMaskWithDecimal; + private subscription: Subscription; + constructor() {} + + public ngOnInit() { + this.subscription = this.group.valueChanges.subscribe(() => { + this.updateLocalState(this.group.value); + }); + } + + public ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public updateLocalState(modelValue): void { + this.localState.isRentedLot = (modelValue.onRentedLot === CreditApplicationValues.CHOICE_YES); + this.localState.showAnnualPropertyTax = (modelValue.onRentedLot === CreditApplicationValues.CHOICE_NO); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.html new file mode 100644 index 0000000..9a84e4b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.html @@ -0,0 +1,66 @@ +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+ +
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.spec.ts new file mode 100644 index 0000000..e31e200 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.spec.ts @@ -0,0 +1,78 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { EthnicityInfoFormComponent } from './ethnicity-info-form.component'; +import { EthnicityInformation } from './ethnicity-information'; +import {TranslateModule} from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + CommonGroups.ETHNICITY_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + return group; +}; + +describe('EthnicityInfoFormComponent', () => { + let component: EthnicityInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: EthnicityInformation; + const customPrefix = 'testPrefix'; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + EthnicityInfoFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(EthnicityInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + component.idPrefix = customPrefix; + component.group.valueChanges.subscribe(state => { + changes = state; + }); + fixture.detectChanges(); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit / allow any and all changes for checkboxes', () => { + const allValuesObject = {'hispanicOrLatino': true, 'mexican': true, 'puertoRican': true, + 'cuban': true, 'otherHispanicLatino': true, 'notHispanicOrLatino': true, 'notProvided': true}; + component.group.patchValue(allValuesObject); + fixture.detectChanges(); + Object.keys(allValuesObject).forEach(key => { + expect(changes[key]).toBe(true); + }); + }); + + it('should emit changes for text-boxes', () => { + component.group.patchValue({'otherHispanicLatinoText': 'Text testing'}); + fixture.detectChanges(); + expect(changes.otherHispanicLatinoText).toBe('Text testing'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.ts new file mode 100644 index 0000000..79baa8b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-info-form.component.ts @@ -0,0 +1,17 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-ethnicity-info-form', + templateUrl: './ethnicity-info-form.component.html', + styleUrls: ['./hmda-styles.scss'] +}) +export class EthnicityInfoFormComponent { + @Input() + public group: FormGroup; + + @Input() + public idPrefix: string; + + constructor() {} +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-information.ts new file mode 100644 index 0000000..45de81f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/ethnicity-information.ts @@ -0,0 +1,10 @@ +export interface EthnicityInformation { + 'hispanicOrLatino'?: boolean; + 'mexican'?: boolean; + 'puertoRican'?: boolean; + 'cuban'?: boolean; + 'otherHispanicLatino'?: boolean; + 'otherHispanicLatinoText'?: string; + 'notHispanicOrLatino'?: boolean; + 'notProvided'?: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.html new file mode 100644 index 0000000..9ed6ffa --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.html @@ -0,0 +1,25 @@ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.spec.ts new file mode 100644 index 0000000..943f129 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.spec.ts @@ -0,0 +1,72 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { GenderInfoFormComponent } from './gender-info-form.component'; +import { GenderInformation } from './gender-information'; +import {TranslateModule} from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + CommonGroups.GENDER_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + return group; +}; + +describe('GenderInfoFormComponent', () => { + let component: GenderInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: GenderInformation; + const customPrefix = 'testPrefix'; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + GenderInfoFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(GenderInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + component.idPrefix = customPrefix; + component.group.valueChanges.subscribe(state => { + changes = state; + }); + fixture.detectChanges(); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit / allow any and all changes for checkboxes', () => { + const allValuesObject = {'male': true, 'female': true, 'notProvided': true}; + component.group.patchValue(allValuesObject); + fixture.detectChanges(); + Object.keys(allValuesObject).forEach(key => { + expect(changes[key]).toBe(true); + }); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.ts new file mode 100644 index 0000000..71e75c1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-info-form.component.ts @@ -0,0 +1,17 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-gender-info-form', + templateUrl: './gender-info-form.component.html', + styleUrls: ['./hmda-styles.scss'] +}) +export class GenderInfoFormComponent { + @Input() + public group: FormGroup; + + @Input() + public idPrefix: string; + + constructor() {} +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-information.ts new file mode 100644 index 0000000..c0b39f5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/gender-information.ts @@ -0,0 +1,5 @@ +export interface GenderInformation { + 'female'?: boolean; + 'male'?: boolean; + 'notProvided'?: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/hmda-styles.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/hmda-styles.scss new file mode 100644 index 0000000..07525de --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/hmda-styles.scss @@ -0,0 +1,3 @@ +.form-group, label{ + margin-bottom: 0 !important; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.html new file mode 100644 index 0000000..76f1c20 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.html @@ -0,0 +1,157 @@ +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.spec.ts new file mode 100644 index 0000000..66e15a0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.spec.ts @@ -0,0 +1,86 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { RaceInfoFormComponent } from './race-info-form.component'; +import { RaceInformation } from './race-information'; +import {TranslateModule} from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + CommonGroups.RACE_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + return group; +}; + +describe('RaceInfoFormComponent', () => { + let component: RaceInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: RaceInformation; + const customPrefix = 'testPrefix'; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + RaceInfoFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(RaceInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + component.idPrefix = customPrefix; + component.group.valueChanges.subscribe(state => { + changes = state; + }); + fixture.detectChanges(); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit / allow any and all changes for checkboxes', () => { + const allValuesObject = { + 'americanIndianAlaskaNative': true, 'blackOrAfricanAmerican': true, 'asian': true, + 'asianIndian': true, 'chinese': true, 'filipino': true, 'japanese': true, 'korean': true, + 'vietnamese': true, 'otherAsian': true, 'hawaiian': true, 'nativeHawaiian': true, 'guamanianChamorro': true, + 'samoan': true, 'otherPacificIslander': true, 'white': true, 'notProvided': true, + }; + component.group.patchValue(allValuesObject); + fixture.detectChanges(); + Object.keys(allValuesObject).forEach(key => { + expect(changes[key]).toBe(true); + }); + }); + + it('should emit changes for text-boxes', () => { + const allValuesObject = { 'otherPacificIslanderText': 'Text testing', + 'americanIndianAlaskaNativeText': 'Text testing', 'otherAsianText': 'Text testing'}; + component.group.patchValue(allValuesObject); + fixture.detectChanges(); + Object.keys(allValuesObject).forEach(key => { + expect(changes[key]).toBe('Text testing'); + }); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.ts new file mode 100644 index 0000000..d6e34d5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-info-form.component.ts @@ -0,0 +1,17 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-race-info-form', + templateUrl: './race-info-form.component.html', + styleUrls: ['./hmda-styles.scss'] +}) +export class RaceInfoFormComponent { + @Input() + public group: FormGroup; + + @Input() + public idPrefix: string; + + constructor() {} +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-information.ts new file mode 100644 index 0000000..4cd5a05 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/hmda/race-information.ts @@ -0,0 +1,22 @@ +export interface RaceInformation { + 'americanIndianAlaskaNative'?: boolean; + 'americanIndianAlaskaNativeText'?: string; + 'blackOrAfricanAmerican'?: boolean; + 'asian'?: boolean; + 'asianIndian'?: boolean; + 'chinese'?: boolean; + 'filipino'?: boolean; + 'japanese'?: boolean; + 'korean'?: boolean; + 'vietnamese'?: boolean; + 'otherAsian'?: boolean; + 'otherAsianText'?: string; + 'hawaiian'?: boolean; + 'nativeHawaiian'?: boolean; + 'guamanianChamorro'?: boolean; + 'samoan'?: boolean; + 'otherPacificIslander'?: boolean; + 'otherPacificIslanderText'?: string; + 'white'?: boolean; + 'notProvided'?: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.html new file mode 100644 index 0000000..cc0a737 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.html @@ -0,0 +1,169 @@ +
+
+
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+
+ +
+ +
+ + + + +
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.spec.ts new file mode 100644 index 0000000..3cf995a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.spec.ts @@ -0,0 +1,183 @@ +import { TestBed, ComponentFixture, async, tick, fakeAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { MemberInfoFormComponent } from './member-info-form.component'; +import { MembershipInformation } from './membership-information'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TranslateModule } from '@ngx-translate/core'; +import { MemberInfoFormService } from './member-info-form.service'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { UiSettings } from 'src/app/shared/configuration/ui-settings'; +import { DefaultSettings } from 'src/app/shared/configuration/default-settings.mock'; +import { configureTestSuite } from 'ng-bullet'; +import { PatriotActComponent } from 'src/app/shared/patriot-act/patriot-act.component'; +import { ElectronicDisclosuresFormComponent } from '../disclosures/electronic-disclosures-form.component'; + +const createRegularTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.MEMBER_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + CommonGroups.MEMBER_INFO_PURPOSE_OF_LOAN_SIGNATURE.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +const createEquityTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.MEMBER_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + CommonGroups.MEMBER_INFO_TERM_OF_LOAN.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + CommonGroups.MEMBER_INFO_PURPOSE_OF_LOAN.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + + +describe('MemberInfoFormComponent', () => { + let component: MemberInfoFormComponent; + let fixture: ComponentFixture; + let applicationWizardService: ApplicationWizardService; + let formBuilder: FormBuilder; + let formGroupEquity: FormGroup; + let formGroupRegular: FormGroup; + let changes: MembershipInformation; + let status: string; + let service: MemberInfoFormService; + let configurationService: ConfigurationService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + MemberInfoFormComponent, + PatriotActComponent, + ElectronicDisclosuresFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroupRegular = createRegularTestFormGroup(formBuilder); + formGroupEquity = createEquityTestFormGroup(formBuilder); + fixture = TestBed.createComponent(MemberInfoFormComponent); + service = TestBed.get(MemberInfoFormService); + + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + component = fixture.componentInstance; + component.group = formGroupRegular; + + configurationService = TestBed.get(ConfigurationService); + configurationService.saveConfiguration(defaultSettings); + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit changes', () => { + component.group.patchValue({'applicantType': '0', 'limitRequested': '10000', 'livesInMaritalPropertyState': '1'}); + expect(changes.applicantType.toString()).toBe('0'); + expect(changes.limitRequested.toString()).toBe('10000'); + expect(changes.livesInMaritalPropertyState.toString()).toBe('1'); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({'applicantType': '0', 'limitRequested': '10000', 'creditScore': '650'}); + expect(changes['creditScore']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + component.group.patchValue({'applicantType': '0', 'limitRequested': '10000'}); + expect(status).toBe('INVALID'); + }); + + it('should emit VALID for complete form', () => { + component.group.patchValue({'applicantType': '0', 'limitRequested': '10000', 'purposeOfLoanSignature': 'Refinance', 'livesInMaritalPropertyState': 'yes'}); + expect(status).toBe('VALID'); + }); + + it('should emit changes ( for equity loans ) ', () => { + applicationWizardService.setLoanType(LoanTypeSet[4].id); + component.group = formGroupEquity; + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + component.group.patchValue({'purposeOfLoan': '0', 'termOfLoan': 120}); + fixture.detectChanges(); + expect(changes.purposeOfLoan.toString()).toBe('0'); + }); + + it('should emit INVALID for incomplete form ( for equity loans )', () => { + applicationWizardService.setLoanType(LoanTypeSet[4].id); + component.group = formGroupEquity; + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + component.group.patchValue({'limitRequested': '10000', 'termOfLoan': -80}); + fixture.detectChanges(); + expect(status).toBe('INVALID'); + }); + + it('should emit VALID for complete form ( for equity loans ) ', () => { + applicationWizardService.setLoanType(LoanTypeSet[5].id); + component.group = formGroupEquity; + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + component.group.patchValue( + {'applicantType': '0', 'limitRequested': '10000', 'livesInMaritalPropertyState': 'yes', 'purposeOfLoan': '1', 'termOfLoan': 120} + ); + fixture.detectChanges(); + expect(status).toBe('VALID'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.ts new file mode 100644 index 0000000..07ed27a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.component.ts @@ -0,0 +1,100 @@ +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MaskingService } from '../../../shared/masking/masking.service'; +import { MemberInfoFormService } from './member-info-form.service'; +import { Subscription } from 'rxjs'; +import { LoanCategory } from 'src/app/shared/loan-type/loan-category'; +import {CreditApplicationValues} from '../../../shared/credit-application-values'; + +@Component({ + selector: 'lw-member-info-form', + templateUrl: './member-info-form.component.html' +}) +export class MemberInfoFormComponent implements OnInit, OnDestroy { + + public numberMask = MaskingService.numberMask; + private subscription: Subscription; + + @Input() + public group: FormGroup; + + @Input() + public amountRequestedName: string; + + @Input() + public hasCoApplicant: boolean; + + @Input() + public askLoanPurpose: boolean; + + @Input() + public askLoanPurposeSignature: boolean; + + @Input() + public askTermDuration: boolean; + + @Input() + public isSpecialLoan: boolean; + + @Input() + public isHelocRateMod: boolean; + + @Input() + public isCreditCard: boolean + + @Input() + public requireAgreement: boolean; + + public purposeOptions = []; + + constructor(private memberService: MemberInfoFormService) {} + + public localState = { + termOptions: [{ value: '', desc: 'Please enter "Loan Amount"'}], + disableDropdown: true + }; + + public ngOnInit() { + if (this.group) { + this.updateLocalState(this.group.value); + } + this.purposeOptions = this.memberService.getPurposeOptions(); + this.askLoanPurposeSignature = this.memberService.requirePurposeOfLoanSignature(); + } + + ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public updateLocalState(modelValues): void { + if (this.subscription) { + this.subscription.unsubscribe(); + } + + if (modelValues.limitRequested) { + this.localState.termOptions = this.memberService.getTermOptions(MaskingService.unMaskNumber(modelValues.limitRequested)); + const loanCategory: LoanCategory = this.memberService.getLoanCategory(); + if (!modelValues.termOfLoan && loanCategory === LoanCategory.Equity) { + this.group.patchValue({termOfLoan: this.localState.termOptions[0].value}); + } + } else { + this.group.patchValue({termOfLoan: ''}); + } + + this.localState.disableDropdown = (this.localState.termOptions.length <= 1); + const dropdownControl = this.group.get('termOfLoan'); + + if (dropdownControl) { + if (this.localState.disableDropdown) { + dropdownControl.disable(); + } else { + dropdownControl.enable(); + } + } + + this.isCreditCard = this.memberService.isCreditCard(); + this.subscription = this.group.valueChanges.subscribe(nextValues => this.updateLocalState(nextValues)); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.service.spec.ts new file mode 100644 index 0000000..f12a010 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.service.spec.ts @@ -0,0 +1,113 @@ +import { ApplicationWizardService } from '../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { MemberInfoFormService } from './member-info-form.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConfigurationService } from '../../../shared/configuration/configuration.service'; +import { UiSettings } from 'src/app/shared/configuration/ui-settings'; +import { DefaultSettings } from 'src/app/shared/configuration/default-settings.mock'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; + +describe('Member Info Form Service', () => { + + let service: MemberInfoFormService; + let applicationWizardService: ApplicationWizardService; + let configurationService: ConfigurationService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + ConfigurationService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(MemberInfoFormService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[5].id); + configurationService = TestBed.get(ConfigurationService); + configurationService.saveConfiguration(defaultSettings); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should get loan type for term options', () => { + spyOn(service, 'getTermOptions').and.callThrough(); + spyOn(applicationWizardService, 'getFieldValue').and.returnValue(LoanTypeSet[5]); + service.getTermOptions(); + expect(applicationWizardService.getFieldValue).toHaveBeenCalled(); + }); + + it('should return term options when given a correct loanId', () => { + applicationWizardService.setLoanType(LoanTypeSet[6].id); + expect(service.getTermOptions('10000')).toBeTruthy(); + expect(service.getTermOptions('10000').length).toBeGreaterThan(1); + }); + + it('should return term options dynamically based on requested loan amount', () => { + applicationWizardService.setLoanType(LoanTypeSet[7].id); + expect(service.getTermOptions('2010').length).toBeLessThan(service.getTermOptions('2019').length); + }); + + it('should return empty term options when given incorrect loanId', () => { + applicationWizardService.setLoanType(LoanTypeSet[3].id); + spyOn(service, 'getTermOptions').and.callThrough(); + expect(service.getTermOptions()).toEqual([]); + }); + + it('should build a list of term options from an array of numbers (signature)', () => { + const listOfPotentialTermLengths = [48, 63, 84]; + expect(service.buildListByFilteringTermOptions(listOfPotentialTermLengths, LoanCode.SIGNATURE)) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'}, + {value: 84, desc: '84 months'} + ]); + }); + + it('should build a list of term options from an array of numbers (heloc)', () => { + const listOfPotentialTermLengths = [48, 63, 84]; + expect(service.buildListByFilteringTermOptions(listOfPotentialTermLengths, LoanCode.EQ_HELOC_Fixed)) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 48, desc: 'Up to 48 months / 4 years'}, + {value: 63, desc: '63 months'}, + {value: 84, desc: '84 months / 7 years'} + ]); + }); + + // tslint:disable-next-line:max-line-length + it('should build a list of term options from an object which has parsable numbers as keys each with a corresponding array of numbers value', () => { + const objectOfListsOfPotentialTermLengths = { + '15150': [48, 63, 84], + '26250': [120, 180] + }; + expect(service.buildListFromRequestedAmountAndTermOptions(objectOfListsOfPotentialTermLengths, '15000')) + .toEqual([ ]); + + expect(service.buildListFromRequestedAmountAndTermOptions(objectOfListsOfPotentialTermLengths, '16000')) + .toEqual([48, 63, 84]); + + expect(service.buildListFromRequestedAmountAndTermOptions(objectOfListsOfPotentialTermLengths, '27000')) + .toEqual([48, 63, 84, 120, 180]); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.service.ts new file mode 100644 index 0000000..86b6c6c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/member-info-form.service.ts @@ -0,0 +1,82 @@ +import { Injectable } from '@angular/core'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { ConfigurationService } from '../../../shared/configuration/configuration.service'; +import { TermOptions } from '../../../shared/options/term-options'; +import { LoanCategory } from 'src/app/shared/loan-type/loan-category'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { SelectOptions } from 'src/app/shared/options/select-options'; + +@Injectable({ + providedIn: 'root' +}) +export class MemberInfoFormService { + constructor( + private applicationWizardService: ApplicationWizardService, + private configurationService: ConfigurationService + ) { } + + public getLoanCategory(): LoanCategory { + return this.applicationWizardService.getFieldValue('loanType').category; + } + + public getPurposeOptions(): Array<{ value: any, desc: string }> { + const loanCode: LoanCode = this.applicationWizardService.getFieldValue('loanType').apiCode; + var options = SelectOptions.purposeOptions; + switch (loanCode) { + case LoanCode.EQ_HELOC_Fixed: + case LoanCode.EQ_HELOC_Flex: + return SelectOptions.purposeOptionsAdditionalRefinance; + default: + return SelectOptions.purposeOptions; + } + } + + public requirePurposeOfLoanSignature(): boolean { + const loanType = this.applicationWizardService.getGroupValues('loanType'); + return (loanType.category === LoanCategory.Signature); + } + + public getTermOptions(requestedAmount?: string): Array<{ value: any, desc: string }> { + const loanCode: LoanCode = this.applicationWizardService.getFieldValue('loanType').apiCode; + const uiSettingKey: string = TermOptions.loanCodeToUiSettingKey[loanCode]; + if (!uiSettingKey) { + return []; + } + let termMonthsOptions = this.configurationService.getConfigurationByField(uiSettingKey); + if (!termMonthsOptions) { + return []; + } + + if (!termMonthsOptions.length) { + termMonthsOptions = this.buildListFromRequestedAmountAndTermOptions(termMonthsOptions, requestedAmount); + } + return this.buildListByFilteringTermOptions(termMonthsOptions, loanCode); + } + + public buildListFromRequestedAmountAndTermOptions(termMonthsOptions: any, requestedAmount?: string) { + let uiTermsFromRequestedAmount = []; + Object.keys(termMonthsOptions).forEach(tier => { + if (parseInt(tier, 10) <= parseInt(requestedAmount, 10)) { + uiTermsFromRequestedAmount = uiTermsFromRequestedAmount.concat(termMonthsOptions[tier]); + } + }); + return uiTermsFromRequestedAmount; + } + + public buildListByFilteringTermOptions(termMonthsOptions: Array, loanCode: LoanCode) { + const uiSettingKey = TermOptions.determineTermOptionsUiSettingsField(loanCode); + let filteredTerms = this.configurationService.getConfigurationByField(uiSettingKey).filter(term => { + return termMonthsOptions.indexOf(term.value) > -1; + }); + if (filteredTerms.length) { + filteredTerms = TermOptions.insertTextDecorationsByLoan(filteredTerms, loanCode); + } + filteredTerms.unshift(TermOptions.defaultTermOption); + return filteredTerms; + } + + public isCreditCard(){ + return this.getLoanCategory() === LoanCategory.CreditCard; + } +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/membership-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/membership-information.ts new file mode 100644 index 0000000..5bedfbe --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/member/membership-information.ts @@ -0,0 +1,13 @@ +import {LoanPurpose} from 'src/app/shared/loan-type/loan-purpose'; + +export interface MembershipInformation { + applicantType: string; + limitRequested: number; + livesInMaritalPropertyState: string; + coApplicantLivesInMaritalPropertyState?: string; + confirmJointCredit?: string; + purposeOfLoanSignature?: string; + purposeOfLoan?: LoanPurpose; + termOfLoan?: number; + agreementRequirement?: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.html new file mode 100644 index 0000000..cc7c70a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.html @@ -0,0 +1,286 @@ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ + +
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ + +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ + FORMS.PERSONAL.SSN_HINT +
+
+
+ +
+ +
+ + +
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+
+ +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ + +
+ +
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ + +
+ +
+ +
+
+
+ +
+ +
+ +
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.spec.ts new file mode 100644 index 0000000..2ef915e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.spec.ts @@ -0,0 +1,105 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { PersonalInfoFormComponent } from './personal-info-form.component'; +import { PersonalInformation } from './personal-information'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TcpaComponent } from '../../../shared/legal-message/tcpa.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { FileUploadComponent } from 'src/app/shared/file-upload/file-upload.component'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.PERSONAL_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('PersonalInfoFormComponent', () => { + let component: PersonalInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: PersonalInformation; + let status: string; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + PersonalInfoFormComponent, + TcpaComponent, + FileUploadComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(PersonalInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit changes', () => { + component.group.patchValue({'firstName': 'Frank', 'lastName': 'Reynolds', 'email': 'MantisToboggan@wolf-cola.org'}); + expect(changes.firstName).toBe('Frank'); + expect(changes.lastName).toBe('Reynolds'); + expect(changes.email).toBe('MantisToboggan@wolf-cola.org'); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({'firstName': 'Frank', 'lastName': 'Reynolds', 'spouseName': 'Barbara'}); + expect(changes['spouseName']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + component.group.patchValue({'firstName': 'Frank', 'lastName': 'Reynolds', 'email': 'MantisToboggan@wolf-cola.org'}); + expect(status).toBe('INVALID'); + }); + + it('should require a valid email address', () => { + component.group.patchValue({'firstName': 'Frank', 'lastName': 'Reynolds', 'email': 'MantisTobogganATwolf-cola.org'}); + expect(component.group.controls.email.status).toBe('INVALID'); + component.group.patchValue({'firstName': 'Frank', 'lastName': 'Reynolds', 'email': 'MantisToboggan@wolf-cola.org'}); + expect(component.group.controls.email.status).toBe('VALID'); + }); + + it('should emit INVALID if primary and co-applicant email addresses are the same', () => { + component.group.patchValue({'primaryApplicantEmail': 'test@test.test', 'coApplicantEmail': 'test@test.test'}); + expect(component.group.controls.coApplicantEmail.status).toBe('INVALID'); + component.group.patchValue({'primaryApplicantEmail': 'test@test.test', 'coApplicantEmail': 'different@test.test'}); + expect(component.group.controls.coApplicantEmail.status).toBe('VALID'); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.ts new file mode 100644 index 0000000..093d939 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-info-form.component.ts @@ -0,0 +1,84 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MaskingService } from '../../../shared/masking/masking.service'; +import { Subscription } from 'rxjs'; +import { ErrorMessageMap } from '../../controls/error-display/error-display.service'; +import { SelectOptions } from '../../../shared/options/select-options'; +import {LoanCategory} from '../../../shared/loan-type/loan-category'; +import { BorrowerType } from 'src/app/shared/borrower-type'; + +@Component({ + selector: 'lw-personal-info-form', + templateUrl: './personal-info-form.component.html' +}) +export class PersonalInfoFormComponent implements OnInit, OnDestroy { + + public dobMask = MaskingService.dobMask; + public phoneMask = MaskingService.phoneMask; + public ssnMask = MaskingService.ssnMask; + public emailMask = MaskingService.emailMask; + public middleInitialMask = MaskingService.middleInitialMask; + public nameErrors = { + 'pattern': 'ERRORS.CUSTOM.ALPHAONLY' + }; + private valueSubscription: Subscription; + + public hideSSN = false; + public phoneTypeOptions = SelectOptions.phoneTypeOptions; + public suffixOptions = SelectOptions.suffixOptions; + + @Input() + public borrowerType: string; + + @Input() + public group: FormGroup; + + @Input() + public isHelocRateMod: boolean; + + @Input() + public showProofOfStudentUploadForm: boolean; + + @Input() + public loanId: string; + + constructor() {} + + ngOnInit() { + this.valueSubscription = this.group.controls.secondaryPhoneNumber.valueChanges.subscribe(() => { + if (this.group.controls.secondaryPhoneNumber.dirty) { + this.group.controls.secondaryPhoneNumberType.markAsDirty(); + } + }); + + if (this.group) { + this.updateLocalState(this.group.value); + } + } + + ngOnDestroy() { + if ( this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + } + + public updateLocalState(modelValues): void { + if (this.isHelocRateMod) { + this.group.patchValue({maritalStatus: 'no'}); + } + } + + public commandToggleSSNView() { + this.hideSSN = !this.hideSSN; + } + + public handleFiles(files: any[]): void { + if(this.borrowerType === BorrowerType.PRIMARY_BORROWER && this.showProofOfStudentUploadForm) { + this.group.patchValue({ 'files': files }); + } + } + + public handleUploadingFiles(isUploading: boolean): void { + this.group.patchValue({ 'isUploading': isUploading }); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-information.ts new file mode 100644 index 0000000..b40cb69 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/personal/personal-information.ts @@ -0,0 +1,22 @@ +import { PhoneTypeEnum } from 'src/app/shared/api-models/api-enums'; +import { ApiSuffix } from 'src/app/shared/api-models/api-suffix'; + +export interface PersonalInformation { + firstName: string; + middleInitial?: string; + lastName: string; + dateOfBirth: string; + suffix?: ApiSuffix; + socialSecurityNumber: string; + primaryPhoneNumberType: PhoneTypeEnum; + primaryPhoneNumber: string; + secondaryPhoneNumberType?: PhoneTypeEnum; + secondaryPhoneNumber?: string; + email: string; + maritalStatus?: string; + coApplicantSpouse?: string; + primaryApplicantEmail?: string; + coApplicantEmail?: string; + isUploading?: boolean; + files?: any[]; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.html new file mode 100644 index 0000000..54b225f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.html @@ -0,0 +1,20 @@ +
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.spec.ts new file mode 100644 index 0000000..a49c16a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.spec.ts @@ -0,0 +1,90 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { DataTableService } from '../../../shared/data-table.service'; +import {TranslateModule} from '@ngx-translate/core'; +import { IsFullInfoSpouseInfoFormComponent } from './is-full-info-spouse-info-form.component'; +import { IsFullInfoSpouse } from './is-full-info-spouse-information'; +import { CreditApplicationValues } from '../../../shared/credit-application-values'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const memberGroup = formBuilder.group({}); + + CommonGroups.IS_FULL_INFO_SPOUSE.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; +}; + +describe('IsFullInfoSpouseInfoFormComponent', () => { + let component: IsFullInfoSpouseInfoFormComponent; + let fixture: ComponentFixture; + let service: DataTableService; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: IsFullInfoSpouse; + let status: string; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + IsFullInfoSpouseInfoFormComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(IsFullInfoSpouseInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + service = TestBed.get(DataTableService); + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit changes', () => { + component.group.patchValue({'isFullInfoSpouse': CreditApplicationValues.CHOICE_YES}); + expect(changes.isFullInfoSpouse).toBe('yes'); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({'isMailingAddressSame': 'maybe'}); + expect(changes['isMailingAddressSame']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + expect(status).toBe('INVALID'); + }); + + it('should emit VALID for complete form', () => { + component.group.patchValue({'isFullInfoSpouse': CreditApplicationValues.CHOICE_NO}); + expect(status).toBe('VALID'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.ts new file mode 100644 index 0000000..ea487fd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-info-form.component.ts @@ -0,0 +1,15 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-is-full-info-spouse-info-form', + templateUrl: './is-full-info-spouse-info-form.component.html' +}) +export class IsFullInfoSpouseInfoFormComponent { + + + @Input() + public group: FormGroup; + + constructor() {} +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-information.ts new file mode 100644 index 0000000..53cd13a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/spouse-type/is-full-info-spouse-information.ts @@ -0,0 +1,3 @@ +export interface IsFullInfoSpouse { + isFullInfoSpouse: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.html new file mode 100644 index 0000000..18f9c5a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.html @@ -0,0 +1,20 @@ +
+ +
+
+ +
+
+
+ +
+ +
+
+
+
+ +
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.spec.ts new file mode 100644 index 0000000..90a125e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.spec.ts @@ -0,0 +1,71 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TcpaComponent } from '../../../shared/legal-message/tcpa.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { AdditionalInfoFormComponent } from './additional-info-form.component'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.SUMMARY_ADDITIONAL_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('AdditionalInfoFormComponent', () => { + let component: AdditionalInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: any; + let status: string; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + AdditionalInfoFormComponent, + TcpaComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(AdditionalInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.ts new file mode 100644 index 0000000..9cebade --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info-form.component.ts @@ -0,0 +1,17 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-additional-info-form', + templateUrl: './additional-info-form.component.html' +}) +export class AdditionalInfoFormComponent { + + @Input() + public group: FormGroup; + @Input() + public modFee: Array<{ "value": any, "desc": string}>; + + constructor() { } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info.ts new file mode 100644 index 0000000..89feb31 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/additional-info.ts @@ -0,0 +1,3 @@ +export interface AdditionalInformation { + modFeeAccount?: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.html new file mode 100644 index 0000000..ffe0fb9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.html @@ -0,0 +1,51 @@ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.spec.ts new file mode 100644 index 0000000..1cb767e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.spec.ts @@ -0,0 +1,71 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { NoteFormComponent } from './note-form.component'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TcpaComponent } from '../../../shared/legal-message/tcpa.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.SUMMARY_NOTE.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('NoteFormComponent', () => { + let component: NoteFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: any; + let status: string; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + NoteFormComponent, + TcpaComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(NoteFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.ts new file mode 100644 index 0000000..4143e29 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/summary/note-form.component.ts @@ -0,0 +1,17 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'lw-note-form', + templateUrl: './note-form.component.html' +}) +export class NoteFormComponent { + + @Input() + public group: FormGroup; + @Input() + public primaryContact: Array; + + constructor() {} + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.html new file mode 100644 index 0000000..3d0b25b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.html @@ -0,0 +1,37 @@ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+
+ +
+ +
+ + +
+ +
+
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.spec.ts new file mode 100644 index 0000000..8fe9e9e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.spec.ts @@ -0,0 +1,88 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule, FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { TextMaskModule } from 'angular2-text-mask'; +import { TcpaComponent } from '../../../shared/legal-message/tcpa.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { TattleSpousePersonalInfoFormComponent } from './tattle-spouse-personal-info-form.component'; +import { TattleSpousePersonalInformation } from './tattle-spouse-personal-information'; +import { configureTestSuite } from 'ng-bullet'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({}); + + CommonGroups.PERSONAL_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; +}; + +describe('TattleSpousePersonalInfoFormComponent', () => { + let component: TattleSpousePersonalInfoFormComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: TattleSpousePersonalInformation; + let status: string; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + TattleSpousePersonalInfoFormComponent, + TcpaComponent + ], + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(TattleSpousePersonalInfoFormComponent); + component = fixture.componentInstance; + component.group = formGroup; + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit changes', () => { + component.group.patchValue({'firstName': 'Frank', 'lastName': 'Reynolds'}); + expect(changes.firstName).toBe('Frank'); + expect(changes.lastName).toBe('Reynolds'); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({'firstName': 'Frank', 'lastName': 'Reynolds', 'spouseName': 'Barbara'}); + expect(changes['spouseName']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + component.group.patchValue({'firstName': 'Frank', 'lastName': 'Reynolds', 'email': 'MantisToboggan@wolf-cola.org'}); + expect(status).toBe('INVALID'); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.ts new file mode 100644 index 0000000..e923301 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-info-form.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MaskingService } from '../../../shared/masking/masking.service'; +import { ErrorMessageMap } from '../../controls/error-display/error-display.service'; + +@Component({ + selector: 'lw-tattle-spouse-personal-info-form', + templateUrl: './tattle-spouse-personal-info-form.component.html' +}) +export class TattleSpousePersonalInfoFormComponent { + + public middleInitialMask = MaskingService.middleInitialMask; + public nameErrors = { + 'pattern': 'ERRORS.CUSTOM.ALPHAONLY' + }; + + @Input() + public group: FormGroup; + + constructor() {} + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-information.ts new file mode 100644 index 0000000..5222df4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-information.ts @@ -0,0 +1,8 @@ +export interface TattleSpousePersonalInformation { + firstName: string; + middleInitial?: string; + lastName: string; + // @TODO: Per Jeff's request this model is subject to change might be changed back. -ZB + // socialSecurityNumber: string; + // email?: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.html new file mode 100644 index 0000000..5d49762 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.html @@ -0,0 +1,536 @@ +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ + + +
+ +
+
+ + +
+ +
+ +
+ +
+ + + + + + + + + + +
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+
+ + +
+
+
ERRORS.CUSTOM.TITLE_BRANDED
+
+ +
+ +
+
+ + +
+
+ +
ERRORS.CUSTOM.LIEN
+
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.spec.ts new file mode 100644 index 0000000..6fb5c4f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.spec.ts @@ -0,0 +1,236 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { CommonGroups } from '../common-groups'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { VehicleCollateralInfoFormComponent } from './vehicle-collateral-info-form.component'; +import { VehicleCollateralInformation } from './vehicle-collateral-information'; +import { TranslateModule } from '@ngx-translate/core'; +import { VehicleCollateralInfoFormService } from './vehicle-collateral-info-form.service'; +import { ConfigurationService } from '../../../shared/configuration/configuration.service'; +import { UiSettings } from '../../../shared/configuration/ui-settings'; +import { DefaultSettings } from '../../../shared/configuration/default-settings.mock'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { HttpClientTestingModule } from '../../../../../node_modules/@angular/common/http/testing'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { StatePipe } from '../../../shared/pipes/state.pipe'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const memberGroup = formBuilder.group({}); + + CommonGroups.VEHICLE_COLLATERAL_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; +}; + +const createPartialTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const memberGroup = formBuilder.group({}); + CommonGroups.VEHICLE_COLLATERAL_INFO_PARTIAL.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, formBuilder.control('', eachField.validation.slice())); + }, this); + return memberGroup; +}; + +describe('VehicleCollateralInfoFormComponent', () => { + let component: VehicleCollateralInfoFormComponent; + let fixture: ComponentFixture; + let applicationWizardService: ApplicationWizardService; + let service: VehicleCollateralInfoFormService; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + let changes: VehicleCollateralInformation; + let configurationService: ConfigurationService; + let status: string; + const defaultSettings: UiSettings = DefaultSettings; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + VehicleCollateralInfoFormComponent, + StatePipe + ], + providers: [ + VehicleCollateralInfoFormService, + ConfigurationService + ], + schemas: [ NO_ERRORS_SCHEMA ] + }).compileComponents(); + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(VehicleCollateralInfoFormComponent); + service = TestBed.get(VehicleCollateralInfoFormService); + + component = fixture.componentInstance; + component.group = formGroup; + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[7].id); + + configurationService = TestBed.get(ConfigurationService); + configurationService.saveConfiguration(defaultSettings); + + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + })); + + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit changes', () => { + component.group.patchValue({'year': 2015}); + fixture.detectChanges(); + expect(changes.year).toBe(2015); + }); + + it('should not accept bad fields', () => { + component.group.patchValue({'minivan': false}); + fixture.detectChanges(); + expect(changes['minivan']).toBeUndefined(); + }); + + it('should emit INVALID for incomplete form', () => { + fixture.detectChanges(); + expect(status).toBe('INVALID'); + }); + + it('should emit VALID for complete form', () => { + component.group.patchValue({ + value: 16850, + type: 'Automobile', + condition: 'New', + year: 2012, + make: 'Subaru', + model: 'Impreza', + mileage: 57000, + term: 36, + loanPurpose: 'Refinance', + seller: 'Private Party', + stateTitled: 'WI', + countyTitled: 'Washington', + files: [ + { + description: "DNR Registration Card", + name: "dnrRegistrationCard.PNG", + }, + { + description: "Boat Title", + name: "boatTitle.PNG" + } + ] + }); + fixture.detectChanges(); + expect(status).toBe('VALID'); + }); + + it('should emit VALID for complete form that requires partial collateral', () => { + component.requireFullCollateral = false; + component.group = createPartialTestFormGroup(formBuilder); + component.group.valueChanges.subscribe(state => { + changes = state; + }); + component.group.statusChanges.subscribe(newStatus => { + status = newStatus; + }); + component.group.patchValue({ + value: 21150, + type: 'Boat', + year: 2015, + make: 'Sun Ray', + model: 'Fishing Boat', + term: 36, + loanPurpose: 'Refinance' + }); + fixture.detectChanges(); + expect(status).toBe('VALID'); + }); + + it('should update local state on new values', () => { + fixture.detectChanges(); + spyOn(component, 'updateTermsBasedOffState').and.callThrough(); + component.group.patchValue({value: 21150}); + fixture.detectChanges(); + expect(component.updateTermsBasedOffState).toHaveBeenCalled(); + }); + + it('should update non changing terms ( valid limit )', () => { + fixture.detectChanges(); + spyOn(component, 'updateNonChangingTerms').and.callThrough(); + spyOn(service, 'getLimitRequested').and.returnValue(30000); + spyOn(service, 'getTerms').and.callThrough(); + component.updateNonChangingTerms(LoanCode.VEHICLE_DEFAULT); + expect(component.updateNonChangingTerms).toHaveBeenCalled(); + expect(service.getLimitRequested).toHaveBeenCalled(); + expect(service.getTerms).toHaveBeenCalled(); + }); + + it('should update non-changing terms ( requires more information )', () => { + applicationWizardService.setLoanType(LoanTypeSet[7].id); + spyOn(component, 'updateNonChangingTerms').and.callThrough(); + spyOn(service, 'getLimitRequested').and.callThrough(); + spyOn(service, 'getTerms').and.callThrough(); + fixture.detectChanges(); + expect(component.localState.termOptions).toEqual([{ value: '', desc: 'Choose One' }]); + expect(component.updateNonChangingTerms).not.toHaveBeenCalled(); + expect(service.getLimitRequested).not.toHaveBeenCalled(); + expect(service.getTerms).toHaveBeenCalled(); + }); + + it('should skip creating a subscription when not default vehicle', () => { + spyOn(component, 'updateTermsBasedOffState').and.callThrough(); + spyOn(component, 'updateNonChangingTerms').and.callThrough(); + spyOn(service, 'getLoanCode').and.returnValue(LoanCode.BOAT); + fixture.detectChanges(); + expect(component.updateTermsBasedOffState).not.toHaveBeenCalled(); + expect(component.updateNonChangingTerms).toHaveBeenCalled(); + }); + + it('should patch previous values for "term" if they are valid (BOAT)', () => { + spyOn(service, 'getLimitRequested').and.returnValue('10000'); + spyOn(service, 'getLoanCode').and.returnValue(LoanCode.BOAT); + spyOn(service, 'getTerms').and.returnValue([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: '36 months'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'} + ]); + fixture.detectChanges(); + expect(component.group.value.term).toBe(''); + component.group.patchValue({term: 63}); + fixture.detectChanges(); + expect(component.group.value.term).toBe(63); + }); + + it('should NOT patch previous values for "term" if they are invalid (BOAT)', () => { + spyOn(service, 'getLimitRequested').and.returnValue('10000'); + spyOn(service, 'getLoanCode').and.returnValue(LoanCode.BOAT); + spyOn(service, 'getTerms').and.returnValue([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: '36 months'}, + {value: 48, desc: '48 months'} + ]); + component.group.patchValue({term: 63}); + fixture.detectChanges(); + expect(component.group.value.term).toBe(''); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.ts new file mode 100644 index 0000000..e3d8c3c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.component.ts @@ -0,0 +1,299 @@ +import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MaskingService } from '../../../shared/masking/masking.service'; +import { Subject, Subscription } from 'rxjs'; +import { VehicleCollateralInfoFormService } from './vehicle-collateral-info-form.service'; +import { TermInputFields } from 'src/app/shared/terms/term-input-fields'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { CreditApplicationValues } from 'src/app/shared/credit-application-values'; +import { AddressInformation } from '../address/address-information'; +import { LoanPurpose } from 'src/app/shared/loan-type/loan-purpose'; +@Component({ + selector: 'lw-vehicle-collateral-info-form', + templateUrl: './vehicle-collateral-info-form.component.html' +}) + +export class VehicleCollateralInfoFormComponent implements OnInit, OnDestroy { + + public numberMask = MaskingService.numberMask; + public yearMask = MaskingService.yearMask; + public phoneMask = MaskingService.phoneMask; + private subscription: Subscription; + private pageSubscription: Subscription; + public stateSet = []; + public removePurchaseContractFiles: Subject; + public removeBoatTitleFiles: Subject; + public removeTrailerTitleFiles: Subject; + + @Input() + public loanId: string; + + @Input() + public requireFullCollateral: boolean; + + @Input() + public requireSellerInput: boolean; + + @Input() + public isTransferringPlate: boolean; + + @Input() + public isVehicleDefault: boolean; + + @Input() + public isTrailerMotorhome: boolean; + + @Input() + public isPowerSport: boolean; + + @Input() + public isBoat: boolean; + + @Input() + public requireBoatAddressInput: boolean; + + @Input() + public hasLien: boolean; + + @Input() + public group: FormGroup; + + @Input() + public boatAddressGroup: FormGroup; + + @Input() + public boatAddressVerifyGroup: FormGroup; + + @Output() + public sendBoatAddress: EventEmitter; + + constructor( + private vehicleService: VehicleCollateralInfoFormService, + private configurationService: ConfigurationService, + private cd: ChangeDetectorRef) { + this.sendBoatAddress = new EventEmitter(); + this.removePurchaseContractFiles = new Subject(); + this.removeBoatTitleFiles = new Subject(); + this.removeTrailerTitleFiles = new Subject(); + } + + public vehicleTitle: string; + public showPrivatePartyUploadForm: boolean; + public showDealerUploadForm: boolean; + public purposeIsPurchase: boolean; + public seller: string; + + public localState = { + termOptions: [{value: '', desc: 'No terms available'}], + typeOptions: [{value: '', desc: 'No types available'}], + loanPurposeOptions: [{value: '', desc: 'No purpose available'}], + conditionOptions: [{value: '', desc: 'No condition available'}], + sellerOptions: [{value: '', desc: 'No seller available'}], + boatTrailerTypeOptions: [{value: '', desc: 'No trailer types available'}], + boatMotorTypeOptions: [{value: '', desc: 'No motor types available'}], + disableDropdown: true, + purposeIsPurchase: false, + showBoatFilesUploadForm: false + }; + + public ngOnInit() { + const loanCode: LoanCode = this.vehicleService.getLoanCode(); + this.stateSet = this.configurationService.getConfigurationByField('stateOptions'); + this.loanId = this.vehicleService.getLoanId(); + this.group.patchValue({ 'files': '' }); + if (this.group) { + this.localState.typeOptions = this.vehicleService.getVehicleTypesBasedOnLoan(loanCode); + const vehicleType = this.vehicleService.getTypeFromLoanCode(loanCode); + this.vehicleTitle = vehicleType.type; + if (!this.localState.typeOptions) { + this.group.patchValue(vehicleType); + } + this.initFormStateFromLoanCode(loanCode); + if (!this.group.value.term) { + this.group.patchValue({term: this.localState.termOptions[0].value}); + } + if (!this.group.value.stateTitled && loanCode === LoanCode.VEHICLE_DEFAULT) { + this.group.patchValue({stateTitled: CreditApplicationValues.STATE_REQUIRED_COAPP}) + } + this.purposeIsPurchase = this.group.value.loanPurpose === LoanPurpose.Purchase; + this.seller = this.group.value.seller; + this.localState.showBoatFilesUploadForm = this.group.value.boatLength > 16; + this.checkSellerForms(this.group.value); + } + this.pageSubscription = this.group.valueChanges.subscribe(nextValues => this.updateLocalState(nextValues)); + } + + ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + if (this.pageSubscription) { + this.pageSubscription.unsubscribe(); + } + } + + public updateLocalState(values) { + this.purposeIsPurchase = values.loanPurpose === LoanPurpose.Purchase; + this.seller = values.seller; + if (!this.purposeIsPurchase) { + this.showDealerUploadForm = undefined; + this.showPrivatePartyUploadForm = undefined; + } + if (values.boatLength > 16 && !this.localState.showBoatFilesUploadForm) { + this.localState.showBoatFilesUploadForm = true; + } + if (this.localState.showBoatFilesUploadForm && values.boatLength <= 16) { + this.removeBoatTitleFiles.next(); + } + this.checkSellerForms(values); + } + + public initFormStateFromLoanCode(loanCode: LoanCode) { + this.updateVehicleNonChangingOptions(loanCode); + if (loanCode === LoanCode.VEHICLE_DEFAULT || loanCode === LoanCode.POWERSPORT) { + this.updateTermsBasedOffState(this.group.value, loanCode); + } else if (loanCode === LoanCode.BOAT || loanCode === LoanCode.TRAILER_MOTORHOME) { + this.updateNonChangingTerms(loanCode); + this.patchDefaultIfTermNotAvailable(); + } else { + console.error('This loan type is not supported, please make sure valid loan type.'); + } + } + + public updateNonChangingTerms(loanCode: LoanCode) { + const limitRequested = this.vehicleService.getLimitRequested(); + if (limitRequested) { + const fields: TermInputFields = new TermInputFields(loanCode, 0, limitRequested, 0); + this.localState.termOptions = this.vehicleService.getTerms(fields); + } else { + this.localState.termOptions = [{value: '', desc: 'No terms available'}]; + this.group.patchValue({termOfLoan: this.localState.termOptions[0].value}); + } + } + + public updateVehicleNonChangingOptions(loanCode: LoanCode) { + this.localState.conditionOptions = this.vehicleService.getOptionsByType("vehicleConditionOptions"); + this.localState.loanPurposeOptions = loanCode === LoanCode.VEHICLE_DEFAULT ? this.vehicleService.getOptionsByType("vehicleLoanPurposeOptionsVehicle") : this.vehicleService.getOptionsByType("vehicleLoanPurposeOptions"); + this.localState.sellerOptions = this.vehicleService.getOptionsByType("vehicleSellerOptions"); + if (loanCode === LoanCode.BOAT) { + this.localState.boatTrailerTypeOptions = this.vehicleService.getOptionsByType("boatTrailerTypeOptions"); + this.localState.boatMotorTypeOptions = this.vehicleService.getOptionsByType("boatMotorTypeOptions"); + } + } + + public patchDefaultIfTermNotAvailable() { + if (this.group.value.term) { + let matched = false; + this.localState.termOptions.forEach((option) => { + if (option.value.toString() === this.group.value.term.toString()) { + matched = true; + } + }); + if (!matched) { + this.group.patchValue({term: this.localState.termOptions[0].value}); + } + } + } + + public updateTermsBasedOffState(modelValues, loanCode): void { + if (this.subscription) { + this.subscription.unsubscribe(); + } + const fields: TermInputFields = new TermInputFields(loanCode, modelValues.year, modelValues.value, modelValues.mileage, modelValues.type); + this.localState.termOptions = this.vehicleService.getTerms(fields); + + if (!this.localState.termOptions.length) { + this.localState.termOptions = [{value: '', desc: 'Choose One'}]; + } + + if (this.localState.termOptions.length > 1) { + this.localState.disableDropdown = false; + } else { + this.localState.disableDropdown = true; + } + + const dropdownControl = this.group.get('term'); + + if (dropdownControl) { + if (this.localState.disableDropdown) { + dropdownControl.disable(); + } else { + dropdownControl.enable(); + } + } + + this.patchDefaultIfTermNotAvailable(); + this.subscription = this.group.valueChanges.subscribe(nextValues => this.updateTermsBasedOffState(nextValues, loanCode)); + } + + public confirmSendBoatAddress(addressInformation) { + this.sendBoatAddress.emit(addressInformation); + } + + public handleFiles(files: any[], component: string): void { + this.group.patchValue({ [component + 'Files']: files }); + } + + public handleUploading(isUploading: boolean, component: string): void { + this.group.patchValue({ [component + 'Uploading']: isUploading }); + } + + public removedBoatTitleFiles(removedAllFiles: boolean) { + if (removedAllFiles) { + this.removeTrailerTitleFiles.next(); + } + } + + public removedTrailerTitleFiles(removedAllFiles: boolean) { + if (removedAllFiles) { + if (this.seller === 'Dealer') { + this.showDealerUploadForm = this.purposeIsPurchase; + this.showPrivatePartyUploadForm = false; + this.cd.detectChanges(); + } + this.localState.showBoatFilesUploadForm = false; + } + } + + public removedPurchaseContractFiles(removedAllFiles: boolean) { + if (removedAllFiles) { + if (this.seller === 'Private_Party') { + this.showPrivatePartyUploadForm = this.purposeIsPurchase; + this.showDealerUploadForm = false; + this.cd.detectChanges(); + } + } + } + + public lastLoanPurpose: string; + + public checkSellerForms(values) { + if (values.seller == 'Private_Party') { + + if (this.showPrivatePartyUploadForm == undefined + || values.loanPurpose != this.lastLoanPurpose) { + this.showPrivatePartyUploadForm = this.purposeIsPurchase; + } + + this.removePurchaseContractFiles.next(); + + } else if (values.seller == 'Dealer') { + + if (this.showDealerUploadForm == undefined) { + this.showDealerUploadForm = this.purposeIsPurchase; + } + + if (!this.isBoat || (this.isBoat && !this.localState.showBoatFilesUploadForm)) { + this.showPrivatePartyUploadForm = false; + this.showDealerUploadForm = this.purposeIsPurchase; + } + + if (this.isBoat && this.localState.showBoatFilesUploadForm) { + this.removeBoatTitleFiles.next(); + } + + } + this.lastLoanPurpose = values.loanPurpose; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.service.spec.ts new file mode 100644 index 0000000..65c27fe --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.service.spec.ts @@ -0,0 +1,106 @@ +import { ApplicationWizardService } from '../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConfigurationService } from '../../../shared/configuration/configuration.service'; +import { UiSettings } from 'src/app/shared/configuration/ui-settings'; +import { DefaultSettings } from 'src/app/shared/configuration/default-settings.mock'; +import { VehicleCollateralInfoFormService } from './vehicle-collateral-info-form.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { TermInputFields } from 'src/app/shared/terms/term-input-fields'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { VehicleType } from 'src/app/shared/vehicle-type/vehicle-type'; + +describe('Vehicle Collateral Info Form Service', () => { + + let service: VehicleCollateralInfoFormService; + let applicationWizardService: ApplicationWizardService; + let configurationService: ConfigurationService; + const defaultSettings: UiSettings = DefaultSettings; + + const mockInputBoat: TermInputFields = new TermInputFields(LoanCode.BOAT, 0, 5000, 0); + const mockInputVehicle2019NonFilter: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 2019, 30000, 1000); + const mockCreditCard: TermInputFields = new TermInputFields(LoanCode.CC_CashBack, 2019, 30000, 1000); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + ConfigurationService + ] + }).compileComponents(); + + service = TestBed.get(VehicleCollateralInfoFormService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[10].id); + configurationService = TestBed.get(ConfigurationService); + configurationService.saveConfiguration(defaultSettings); + })); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should get pwc type options', () => { + spyOn(service, 'getLimitRequested').and.callThrough(); + spyOn(applicationWizardService, 'getFieldValue').and.callThrough(); + service.getLimitRequested(); + expect(service.getLimitRequested).toHaveBeenCalled(); + expect(applicationWizardService.getFieldValue).toHaveBeenCalled(); + }); + + it('should get correct vehicle type based off loanCode', () => { + spyOn(service, 'getTypeFromLoanCode').and.callThrough(); + let term = service.getTypeFromLoanCode(LoanCode.BOAT); + expect(term).toEqual({type: VehicleType.BOAT}); + term = service.getTypeFromLoanCode(LoanCode.TRAILER_MOTORHOME); + expect(term).toEqual({type: VehicleType.TRAILER}); + term = service.getTypeFromLoanCode(LoanCode.VEHICLE_PRE_APPROVAL); + expect(term).toEqual({type: VehicleType.AUTO}); + expect(service.getTypeFromLoanCode).toHaveBeenCalled(); + }); + + it('should get correct correct vehicle calculator and terms based off loanCode(boat)', () => { + spyOn(service, 'getTerms').and.callThrough(); + const terms = service.getTerms(mockInputBoat); + expect(terms) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 63, desc: 'Up to 63 months'}, + ]); + expect(service.getTerms).toHaveBeenCalled(); + }); + + it('should get correct correct vehicle calculator and terms based off loanCode(vehicle)', () => { + spyOn(service, 'getTerms').and.callThrough(); + const terms = service.getTerms(mockInputVehicle2019NonFilter); + expect(terms) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'}, + {value: 75, desc: '75 months'}, + {value: 84, desc: '84 months'}, + ]); + expect(service.getTerms).toHaveBeenCalled(); + }); + + it('should throw error with invalid loanCode (creditCard)', () => { + spyOn(service, 'getTerms').and.callThrough(); + expect(function() { service.getTerms(mockCreditCard); }) + .toThrow(new Error('Cannot construct Available Terms Calculator from UI Settings')); + expect(service.getTerms).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.service.ts new file mode 100644 index 0000000..ac958a0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-info-form.service.ts @@ -0,0 +1,94 @@ +import { ApplicationWizardService } from '../../application-wizard.service'; +import { ConfigurationService } from '../../../shared/configuration/configuration.service'; +import { Injectable } from '@angular/core'; +import { TermOptions } from '../../../shared/options/term-options'; +import { TermInputFields } from 'src/app/shared/terms/term-input-fields'; +import { VehicleTermCalculator } from 'src/app/shared/terms/vehicle-term-calculator'; +import { DefaultTermCalculator } from 'src/app/shared/terms/default-term-calculator'; +import { AvailableTermsCalculator } from 'src/app/shared/terms/available-terms-calculator'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { VehicleOptions } from 'src/app/shared/options/vehicle-options'; +import { VehicleType } from 'src/app/shared/vehicle-type/vehicle-type'; + +@Injectable({ + providedIn: 'root' +}) +export class VehicleCollateralInfoFormService { + + private defaultType: string; + + constructor( + private applicationWizardService: ApplicationWizardService, + private configurationService: ConfigurationService + ) {} + + public getLoanCode(): LoanCode { + return this.applicationWizardService.getFieldValue('loanType').apiCode; + } + + public getLoanId() : string { + return this.applicationWizardService.getFieldValue("loanId"); + } + + public getLimitRequested(): number { + return this.applicationWizardService.getFieldValue('limitRequested'); + } + + public getOptionsByType(options: String): Array<{ value: any, desc: string}> { + return VehicleOptions[`${options}`]; + } + + public getVehicleTypesBasedOnLoan(loanCode: LoanCode): Array<{ value: VehicleType, desc: string }> { + return VehicleOptions.getVehicleTypesByLoanCode(loanCode); + } + + public getTypeFromLoanCode(loanCode: LoanCode): { type: VehicleType } { + if (loanCode === LoanCode.BOAT) { + return {type: VehicleType.BOAT}; + } else if (loanCode === LoanCode.TRAILER_MOTORHOME) { + return {type: VehicleType.TRAILER}; + } else if ([LoanCode.VEHICLE_DEFAULT, LoanCode.VEHICLE_PRE_APPROVAL].includes(loanCode)) { + return {type: VehicleType.AUTO}; + } else if (loanCode === LoanCode.POWERSPORT) { + return {type: VehicleType.PWC}; + } + return null; + } + + public getTerms(fields: TermInputFields): Array<{ value: any, desc: string }> { + const uiSettingKey: string = TermOptions.loanCodeToUiSettingKey[fields.type]; + if (!uiSettingKey) { + throw new Error('Cannot construct Available Terms Calculator from UI Settings'); + } + const termMonthsOptions = this.configurationService.getConfigurationByField(uiSettingKey); + if (!termMonthsOptions) { + throw new Error('Cannot construct Available Terms Calculator from UI Settings'); + } + return this.getTermCalculator(fields, termMonthsOptions).determineTerm(); + } + + public getTermCalculator(fields: TermInputFields, termMonthsOptions: any): AvailableTermsCalculator { + switch (fields.type) { + case LoanCode.TRAILER_MOTORHOME: + return new DefaultTermCalculator( + fields, termMonthsOptions, this.configurationService.getConfigurationByField(TermOptions.uiSettingsKeyWithoutYear) + ); + case LoanCode.BOAT: + return new DefaultTermCalculator( + fields, termMonthsOptions, this.configurationService.getConfigurationByField(TermOptions.uiSettingsKeyWithoutYear) + ); + case LoanCode.POWERSPORT: + return new DefaultTermCalculator( + fields, termMonthsOptions, this.configurationService.getConfigurationByField(TermOptions.uiSettingsKeyWithoutYear) + ); + case LoanCode.VEHICLE_DEFAULT: + return new VehicleTermCalculator( + fields, termMonthsOptions, this.configurationService.getConfigurationByField(TermOptions.uiSettingsKeyWithoutYear) + ); + default: + return new DefaultTermCalculator( + fields, termMonthsOptions, this.configurationService.getConfigurationByField(TermOptions.uiSettingsKeyWithoutYear) + ); + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-information.ts new file mode 100644 index 0000000..b113a86 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-information.ts @@ -0,0 +1,44 @@ +import { VehicleType } from 'src/app/shared/vehicle-type/vehicle-type'; +import { AddressInformation } from '../address/address-information'; + +export interface VehicleCollateralInformation { + value: number; + condition: string; // [New, Used] + type: VehicleType; + year: number; + make: string; + model: string; + mileage: number; + term: string; + loanPurpose: string; // [ Refinance, Purchase, Lease Payoff, Other ] + seller?: string; // [Private Party, Dealership] + vin?: string; + transferring?: string; + licensePlateNumber?: string; + dealerName?: string; + dealerPhone?: string; + stateTitled?: string; + countyTitled?: string; + titleBranded?: boolean; + lien?: boolean; + boatLength?: number; + boatSameAddress?: string; + boatAddress?: AddressInformation; + trailerYear?: number; + trailerMake?: string; + trailerType?: string; // [Single, Tandem, Tri-Axle] + motorYear?: number; + motorMake?: string; + motorModel?: string; + motorType?: number; // [Inboard, Outboard, Inboard/Outboard] + municipalityType?: string; + municipalityWhereKept?: string; + dnrRegistrationCardFiles?: any[]; + purchaseContractFiles?: any[]; + boatTitleFiles?: any[]; + trailerTitleFiles?: any[]; + dnrRegistrationCardUploading?: boolean; + purchaseContractUploading?: boolean; + boatTitleUploading?: boolean; + trailerTitleUploading?: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.component.spec.ts new file mode 100644 index 0000000..8f4b02c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.component.spec.ts @@ -0,0 +1,53 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { InputIconComponent } from '../../controls/input-icon/input-icon.component'; +import { ErrorDisplayComponent } from '../../controls/error-display/error-display.component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { JointCreditFormComponent } from './joint-credit-confirmation.component'; +import { Subject } from 'rxjs'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Joint Credit Confirmation Component', () => { + let component: JointCreditFormComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + InputIconComponent, + ErrorDisplayComponent, + JointCreditFormComponent + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(JointCreditFormComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should subscribe to prop changes from parent component', () => { + component.openModal = new Subject(); + spyOn(component, 'open').and.callThrough(); + fixture.detectChanges(); + expect(component.open).not.toHaveBeenCalled(); + component.openModal.next(false); + expect(component.open).not.toHaveBeenCalled(); + component.openModal.next(true); + expect(component.open).toHaveBeenCalled(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.component.ts new file mode 100644 index 0000000..9a41dab --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.component.ts @@ -0,0 +1,64 @@ +import { Component, ViewChild, Input, OnInit, OnDestroy } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { FormGroup } from '@angular/forms'; +import { CreditApplicationValues } from '../../../shared/credit-application-values'; +import { Subject, Subscription } from 'rxjs'; + +@Component({ + selector: 'lw-joint-credit-confirmation', + templateUrl: './joint-credit-confirmation.html' +}) +export class JointCreditFormComponent implements OnInit, OnDestroy { + + @Input() + public commandJointCredit: Function; + @Input() + public commandIndividual: Function; + @Input() + public openModal: Subject; + + private subscription: Subscription; + + constructor(private modalService: NgbModal) {} + + @ViewChild('jointCredit') modalContent: any; + + public open() { + this.modalService.open(this.modalContent, {size: 'sm'}).result.then((res) => { + + if (res === 'Joint Credit') { + // Does this change the response to applicantType? or the response to confirmJointCredit? -ZB + this.handleJointCredit(); + } else if (res === 'Individual') { + this.handleIndividual(); + } + }, () => { + this.handleIndividual(); + }); + } + + ngOnInit() { + if (this.openModal) { + this.subscription = this.openModal.subscribe((shouldOpen: boolean) => { + if (shouldOpen) { + this.modalContent.elementRef.nativeElement.ownerDocument.activeElement.blur(); + this.open(); + } + }); + } + } + + ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public handleJointCredit() { + // @TODO: Does closing this modal alert ever do anything else? + } + + public handleIndividual() { + this.commandIndividual(); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.html new file mode 100644 index 0000000..09e01d5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/warning-modals/joint-credit-confirmation.html @@ -0,0 +1,16 @@ + + + + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/wizard-validators.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/wizard-validators.ts new file mode 100644 index 0000000..19739e8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/field-groups/wizard-validators.ts @@ -0,0 +1,330 @@ +import { AbstractControl, ValidationErrors, ValidatorFn, Validators, FormGroup, FormControl } from '@angular/forms'; +import { CreditApplicationValues } from '../../shared/credit-application-values'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { MaskingService } from '../../shared/masking/masking.service'; +import { LoanTitle } from 'src/app/shared/loan-type/loan-title'; +import { LoanPurpose } from 'src/app/shared/loan-type/loan-purpose'; + +export class WizardValidators { + + // @TODO This must also check for a "FieldDependsOnME" validator on the other field and throw if not there. + // Possibly just do it in a test, which would be better for performance anyways. + public static RequiredIfValidator(otherField: string): ValidatorFn { + return this.RequiredIfMultipleValidator([otherField]); + } + + public static RequiredIfMultipleValidator(otherFields: Array): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.parent) { + let allFound = true; + otherFields.forEach((eachField) => { + const dependentField = control.parent.get(eachField); + allFound = allFound && dependentField.value; + }, this); + if (allFound) { + return Validators.required(control); + } + } + return null; + }; + } + + public static RentedLotValidator(loanType): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + return LoanCode.MANUFACTURED_HOME_LOAN === loanType.apiCode ? Validators.required(control) : null; + }; + } + + public static LienPositionValidator(loanType): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + return LoanCode.MANUFACTURED_HOME_LOAN !== loanType.apiCode ? Validators.required(control) : null; + }; + } + + public static RentPaidValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.parent) { + const isRented = control.parent.get('onRentedLot'); + if (isRented && isRented.value === CreditApplicationValues.CHOICE_YES) { + return Validators.required(control); + } else { + return null; + } + } + }; + } + + public static AnnualPropertyTaxValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.parent) { + const isRented = control.parent.get('onRentedLot'); + if (isRented && isRented.value === CreditApplicationValues.CHOICE_NO) { + return Validators.required(control); + } else { + return null; + } + } + }; + } + + public static CoApplicantValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.parent) { + const coApplicant = control.parent.get('applicantType'); + const coApplicantLivesInMaritalPropertyState = control.parent.get('coApplicantLivesInMaritalPropertyState'); + const confirmJointCredit = control.parent.get('confirmJointCredit'); + const yes = CreditApplicationValues.CHOICE_YES; + if (!confirmJointCredit || !coApplicantLivesInMaritalPropertyState) { + return null; + } + if (coApplicant.value === yes && coApplicantLivesInMaritalPropertyState.value !== yes && confirmJointCredit.value !== yes) { + coApplicantLivesInMaritalPropertyState.setValidators(Validators.required); + confirmJointCredit.setValidators(Validators.required); + const errors = {required: true}; + coApplicantLivesInMaritalPropertyState.setErrors(errors); + confirmJointCredit.setErrors(errors); + } else if (coApplicant.value === CreditApplicationValues.CHOICE_NO && + coApplicantLivesInMaritalPropertyState && + confirmJointCredit) { + const errors = coApplicantLivesInMaritalPropertyState.errors; + if (errors) { + coApplicantLivesInMaritalPropertyState.setErrors({}); + confirmJointCredit.setErrors({}); + } + if (coApplicantLivesInMaritalPropertyState.validator) { + coApplicantLivesInMaritalPropertyState.clearValidators(); + confirmJointCredit.clearValidators(); + } + } + } + return null; + }; + } + + public static FieldDependsOnMe(otherField: string): ValidatorFn { + return this.FieldsDependsOnMe([otherField]); + } + + public static FieldsDependsOnMe(otherFields: Array): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.parent) { + otherFields.forEach((eachField) => { + const dependentField = control.parent.get(eachField); + if (!dependentField) { + throw new Error('Dependent field not found ' + eachField); + } + dependentField.updateValueAndValidity(); + }, this); + } + return null; + }; + } + + public static RequireSpecificStateForAddressValidator(doesRequireSpecificState: boolean = false, requiredState: string): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + if (control.value !== requiredState && doesRequireSpecificState) { + return {'requiredState': {state: requiredState}}; + } + return null; + }; + } + + public static RequireWisconsinCollateralValidator(enabled: boolean = false, primaryBorrowerState?: string): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + let addressInformationState = ''; + try { + addressInformationState = control.parent.parent.get('addressInformation').get('state').value; + } catch (ex) { + addressInformationState = ''; + } + + if (control.value === CreditApplicationValues.CHOICE_YES && primaryBorrowerState !== 'WI' && enabled) { + return {'requireWisconsinCollateral': true}; + } else if (control.value === CreditApplicationValues.CHOICE_NO && addressInformationState !== 'WI' && enabled) { + return {'requireWisconsinCollateral': true}; + } else { + return null; + } + }; + } + + public static UpdateCollateralMessage(enabled: boolean = false): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + let isAddressSameComponent; + try { + isAddressSameComponent = control.parent.parent.get('collateralInformation').get('isAddressSame'); + } catch (ex) { + isAddressSameComponent = undefined; + } + + if (isAddressSameComponent) { + isAddressSameComponent.updateValueAndValidity(); + } + + return; + }; + } + + // 1. Control is actually a "FormGroup" + // 2. Return any error you want, with any info you want. + // 3. Can use "compose" if needed. + public static PersonalInfoValidator = (control: AbstractControl): { [key: string]: boolean } => { + // This is straight forward for one field, but multiple? + const secondaryPhone = control.get('secondaryPhoneNumber'); + const secondaryType = control.get('secondaryPhoneNumberType'); + if (!secondaryPhone || !secondaryType) { + return null; + } + // Could use a side-effect strategy where we call 'setErrors' on the field and then add/remove the errors ourselves + if (secondaryPhone.value && !secondaryType.value && secondaryType.touched) { + secondaryType.setErrors(Object.assign({}, secondaryType.errors, {required: true})); + } else { + const errors = secondaryType.errors; + if (errors.required) { + delete errors.required; + secondaryType.setErrors(errors); + } + } + // Could also return the error, but it would live at the group level. + return null; + } + + public static HmdaGenderValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.parent) { + const female = control.parent.get('female'); + const male = control.parent.get('male'); + if (female.value === true && male.value === true) { + control.setErrors({incorrect: true}); + return {incorrect: true}; + } else { + control.setErrors( null); + return null; + } + } + }; + } + + public static RequireOneValidator(): ValidatorFn { + return (control: FormGroup): ValidationErrors | null => { + const siblings = control.controls; + let hasValue = false; + Object.keys(siblings).forEach((child: string) => { + if (siblings[child].value) { + hasValue = true; + return null; + } + }); + if (!hasValue) { + return {noneSelected: true}; + } + }; + } + + public static maxCollateralValueValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = MaskingService.unMaskNumber(control.value); + if (value < 1000000000) { + return null; + } else { + return {maxCurrency: {max: 999999999, actual: value}}; + } + }; + } + + public static HelocRateModAppValidator(loanType): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + return LoanTitle.HELOC_RateMod !== loanType.title ? Validators.required(control) : null; + }; + } + + public static ListedForSaleValidator(loanType): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + return ![LoanTitle.ManufacturedHome, LoanTitle.HELOC_RateMod].includes(loanType.title) ? Validators.required(control) : null; + }; + } + + public static VehicleDefaultValidator(loanType): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + return LoanCode.VEHICLE_DEFAULT == loanType.apiCode ? Validators.required(control) : null; + }; + } + + public static RequireTransferring(loanType): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.parent) { + const value = control.parent.get("loanPurpose") ? control.parent.get("loanPurpose").value : ""; + return [LoanCode.VEHICLE_DEFAULT, LoanCode.TRAILER_MOTORHOME].includes(loanType.apiCode) && value === LoanPurpose.Purchase ? Validators.required(control) : null; + } + }; + } + + public static RequireSeller(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.parent) { + const value = control.parent.get("loanPurpose") ? control.parent.get("loanPurpose").value : ""; + return (value === LoanPurpose.Purchase ) ? Validators.required(control) : null; + } + }; + } + + public static ValueEqual(otherControl: string, title: string): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (control.parent) { + const otherValue = control.parent.get(otherControl); + if (otherValue.value && otherValue.value.toLowerCase() === control.value.toLowerCase() && control.value) { + return {sameValue: { name: title }}; + } else { + return null; + } + } + }; + } + + public static FileValidator(files: FormControl): ValidationErrors | null{ + if(files.value) { + if (files.value.length > 0) { + return files.value.filter(file => file.invalid == true).length > 0 ? { invalidFiles: true } : null; + } + } + } + + public static MustBeFalse(isUploading: FormControl): ValidationErrors | null{ + if(isUploading.value === true) { + return { invalidFiles: true }; + } + return null; + } + + public static BrandedTitleValidator(): ValidatorFn { + return (group: FormGroup): ValidationErrors | null => { + if (group.controls.titleBranded) { + const titleBranded = group.controls.titleBranded ? group.controls.titleBranded.value : ""; + const loanPurpose = group.controls.loanPurpose ? group.controls.loanPurpose.value : ""; + const seller = group.controls.seller ? group.controls.seller.value : ""; + return titleBranded === CreditApplicationValues.CHOICE_YES + && loanPurpose === LoanPurpose.Purchase + && seller == 'Private_Party' ? { titleBranded: true } : null; + } + }; + } + + public static NumberMaxLengthValidator(max: Number): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = MaskingService.unMaskNumber(control.value); + if (value.length > max) { + return { maxlength: {requiredLength: max, actual: value} } + } + }; + } + + public static VINMaxLength(loanType: any): ValidatorFn { + if (loanType.apiCode == LoanCode.BOAT) { + return Validators.maxLength(60) + } else { + return Validators.maxLength(20) + } + } +} + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.html new file mode 100644 index 0000000..5dfbccf --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.html @@ -0,0 +1,16 @@ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.spec.ts new file mode 100644 index 0000000..d83d528 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.spec.ts @@ -0,0 +1,53 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { LoanWizardComponent } from './loan-wizard.component'; +import { Component } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; + +@Component({ + selector: 'lw-waypoints', + template: '

Waypoints stub

' +}) +class MockWaypointsComponent { + + constructor() { } + +} + +describe('LoanWizardComponent', () => { + let component: LoanWizardComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + MockWaypointsComponent, + LoanWizardComponent + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoanWizardComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should have a router outlet', () => { + const outlet = fixture.debugElement.query(By.css('router-outlet')).nativeElement; + expect(outlet).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.ts new file mode 100644 index 0000000..93877d2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/loan-wizard.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-root', + templateUrl: './loan-wizard.component.html', + styleUrls: ['./loan-wizard.component.scss'] +}) +export class LoanWizardComponent { + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.html new file mode 100644 index 0000000..83f2762 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.html @@ -0,0 +1,59 @@ +
+
+
+

+

+
+
COPY.RESIDENCE.{{localState.borrowerType}}
+
+
+ + + + + + +
+ + + + + + + + + +
+

HEADINGS.ADDITIONAL_RESIDENCE_A

+

HEADINGS.ADDITIONAL_RESIDENCE_B

+ + +
+
+ +
+ + +
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.spec.ts new file mode 100644 index 0000000..9c4e3da --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.spec.ts @@ -0,0 +1,545 @@ +import {TestBed, async, ComponentFixture, fakeAsync, tick} from '@angular/core/testing'; +import {RouterTestingModule} from '@angular/router/testing'; +import {By} from '@angular/platform-browser'; +import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; +import {ReactiveFormsModule} from '@angular/forms'; +import {AddressInfoPageComponent} from './address-info-page.component'; +import {NO_ERRORS_SCHEMA} from '@angular/core'; +import {CreditApplicationDaoService} from '../../../../shared/credit-application-dao.service'; +import {AddressInfoPageService} from './address-info-page.service'; +import {MockCreditApplicationDaoService} from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import {AddressInfoFormComponent} from '../../../field-groups/address/address-info-form.component'; +import {ResidenceInfoFormComponent} from '../../../field-groups/address/residence-info-form.component'; +import {TranslateModule} from '@ngx-translate/core'; +import {BorrowerType} from '../../../../shared/borrower-type'; +import {apiTranslatorPreTranslate} from '../../../../testing/shared-mocks/api-translator-pre-post'; +import {StatePipe} from '../../../../shared/pipes/state.pipe'; +import {ConfigurationService} from '../../../../shared/configuration/configuration.service'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {UiSettings} from '../../../../shared/configuration/ui-settings'; +import {DefaultSettings} from '../../../../shared/configuration/default-settings.mock'; +import {configureTestSuite} from 'ng-bullet'; +import {AddressValidationService} from 'src/app/shared/address-validation/address-validation.service'; +import {AddressInformation} from 'src/app/loan-wizard/field-groups/address/address-information'; +import {CreditApplicationValues} from 'src/app/shared/credit-application-values'; +import {ApplicationWizardService} from 'src/app/loan-wizard/application-wizard.service'; +import {LoanTypeSet} from 'src/app/shared/loan-type'; +import {MemberService} from '../../../../member/member.service'; + +describe('AddressInfoPageComponent', () => { + let component: AddressInfoPageComponent; + let fixture: ComponentFixture; + let service: AddressInfoPageService; + let memberService: MemberService; + let configService: ConfigurationService; + let addressValidationService: AddressValidationService; + let applicationWizardService: ApplicationWizardService; + const defaultSettings: UiSettings = DefaultSettings; + + const mockPrimaryAddress1: AddressInformation = apiTranslatorPreTranslate.fullInfoBorrowers[0].addressInformation; + const mockPrimaryResidence1 = { + 'isMailingAddressSame': true, + 'residenceMonthlyPayment': 500, + 'residenceTimeYears': '1', + 'residenceTimeMonths': '0', + 'residenceStatus': '0' + }; + const mockPrimaryResidence2 = { + 'isMailingAddressSame': true, + 'residenceMonthlyPayment': 500, + 'residenceTimeYears': '2', + 'residenceTimeMonths': '0', + 'residenceStatus': '0' + }; + const mockPrimaryResidenceMailing = { + 'isMailingAddressSame': false, + 'residenceMonthlyPayment': 500, + 'residenceTimeYears': '8', + 'residenceTimeMonths': '5', + 'residenceStatus': '0' + }; + const mockPriorAddress1: AddressInformation = { + 'addressLine1': '321 Main St', + 'addressLine2': 'Apt 100', + 'zipcode': '53203', + 'city': 'Milwaukee', + 'state': 'WI' + }; + const mockPriorResidence1 = { + 'residenceTimeYears': '0', + 'residenceTimeMonths': '6', + 'residenceStatus': '0' + }; + const mockPriorAddress2: AddressInformation = { + 'addressLine1': '999 Main St', + 'addressLine2': 'Apt 101', + 'zipcode': '53204', + 'city': 'Milwaukee', + 'state': 'WI' + }; + const mockPriorResidence2 = { + 'residenceTimeYears': '0', + 'residenceTimeMonths': '6', + 'residenceStatus': '0' + }; + + + const mockMailingAddress1 = { + addressLine1: '111 S. Mailing St.', + addressLine2: '', + city: 'mail-city', + state: 'WI', + zipcode: '12354' + }; + const mockIsSameAddressSame = {isAddressSame: 'yes'}; + const mockIsSameAddressDiff = {isAddressSame: 'no'}; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + AddressInfoPageComponent, + AddressInfoFormComponent, + ResidenceInfoFormComponent, + StatePipe + ], + providers: [ + ConfigurationService, + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService}, + AddressValidationService + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddressInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(AddressInfoPageService); + memberService = TestBed.get(MemberService); + addressValidationService = TestBed.get(AddressValidationService); + configService = TestBed.get(ConfigurationService); + configService.saveConfiguration(defaultSettings); + spyOn(service, 'getNextTopicMessage').and.callThrough().and.returnValue('foo bar'); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create for primary borrower', () => { + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.PRIMARY_BORROWER); + spyOn(service, 'individualIsKnownSpouse').and.returnValue(false); + fixture.detectChanges(); + expect(service.determineBorrowerType).toHaveBeenCalled(); + expect(service.individualIsKnownSpouse).toHaveBeenCalled(); + expect(component).toBeTruthy(); + expect(component.pageForm).toBeTruthy(); + expect(component.isAddressSameGroup).toBeFalsy(); + expect(component.addressInfoGroup).toBeTruthy(); + expect(component.mailingInfoGroup).toBeFalsy(); // group isn't added by default + expect(component.priorAddressInfoGroup).toBeTruthy(); + expect(component.residenceInfoGroup).toBeTruthy(); + expect(component.priorResidenceInfoGroup).toBeTruthy(); + expect(component.localState.showPriorAddressCount).toEqual(0); + }); + + it('should create for known spouse, is same address question un-answered', () => { + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.CO_BORROWER); + spyOn(service, 'individualIsKnownSpouse').and.returnValue(true); + fixture.detectChanges(); + expect(service.determineBorrowerType).toHaveBeenCalled(); + expect(service.individualIsKnownSpouse).toHaveBeenCalled(); + expect(component).toBeTruthy(); + expect(component.pageForm).toBeTruthy(); + expect(component.isAddressSameGroup).toBeTruthy(); + // Nothing else present, the question hasn't been answered. + expect(component.addressInfoGroup).toBeFalsy(); + expect(component.mailingInfoGroup).toBeFalsy(); + expect(component.priorAddressInfoGroup).toBeFalsy(); + expect(component.residenceInfoGroup).toBeFalsy(); + expect(component.priorResidenceInfoGroup).toBeFalsy(); + expect(component.localState.showPriorAddressCount).toEqual(0); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + expect(component.commandPrevious).not.toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + fixture.detectChanges(); + spyOn(service, 'saveAndNavigate'); + component.localState.nextDisabled = false; + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + spyOn(service, 'cancelAndNavigate'); + component.commandPrevious(); + expect(service.cancelAndNavigate).toHaveBeenCalled(); + }); + + it('should bind the previous button to the correct command function', () => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonPrevious')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).not.toHaveBeenCalled(); + expect(component.commandPrevious).toHaveBeenCalled(); + }); + }); + + it('should reveal hidden form fields (mailing address)', async () => { + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.PRIMARY_BORROWER); + spyOn(service, 'individualIsKnownSpouse').and.returnValue(false); + fixture.detectChanges(); + + expect(fixture.debugElement.nativeElement.querySelector('#mailingInfo-inputAddress1')).toBeFalsy(); + + const preClickInputs = fixture.debugElement.queryAll(By.css('input')); + const preClickSelects = fixture.debugElement.queryAll(By.css('select')); + + fixture.debugElement.nativeElement.querySelector('#addressInfo-isMailingSameYes').click(); + fixture.detectChanges(); + + const postClickInputs = fixture.debugElement.queryAll(By.css('input')); + const postClickSelects = fixture.debugElement.queryAll(By.css('select')); + + expect(preClickInputs.length + preClickSelects.length).toBeLessThan(postClickInputs.length + postClickSelects.length); + expect(fixture.debugElement.nativeElement.querySelector('#mailingInfo-inputAddress1')).toBeTruthy(); + + fixture.debugElement.nativeElement.querySelector('#addressInfo-isMailingSameYes').click(); + fixture.detectChanges(); + + const postSecondClickInputs = fixture.debugElement.queryAll(By.css('input')); + const postSecondClickSelects = fixture.debugElement.queryAll(By.css('select')); + + fixture.whenStable().then(() => { + expect(preClickInputs.length + preClickSelects.length).toBe(postSecondClickInputs.length + postSecondClickSelects.length); + expect(fixture.debugElement.nativeElement.querySelector('#mailingInfo-inputAddress1')).toBeFalsy(); + }); + }); + + it('should add and remove previous address sets as needed', async () => { + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.PRIMARY_BORROWER); + spyOn(service, 'individualIsKnownSpouse').and.returnValue(false); + fixture.detectChanges(); + + const preClickInputs = fixture.debugElement.queryAll(By.css('input')); + const preClickSelects = fixture.debugElement.queryAll(By.css('select')); + + expect(component.localState.showPriorAddressCount).toEqual(0); + component.residenceInfoGroup.patchValue({ + residenceTimeYears: '1', + residenceTimeMonths: '0' + }); + fixture.detectChanges(); + expect(component.localState.showPriorAddressCount).toEqual(1); + + const postClickInputs = fixture.debugElement.queryAll(By.css('input')); + const postClickSelects = fixture.debugElement.queryAll(By.css('select')); + + expect(preClickInputs.length + preClickSelects.length).toBeLessThan(postClickInputs.length + postClickSelects.length); + + component.residenceInfoGroup.patchValue({ + residenceTimeYears: '2', + residenceTimeMonths: '0' + }); + fixture.detectChanges(); + expect(component.localState.showPriorAddressCount).toEqual(0); + + const postSecondClickInputs = fixture.debugElement.queryAll(By.css('input')); + const postSecondClickSelects = fixture.debugElement.queryAll(By.css('select')); + + expect(preClickInputs.length + preClickSelects.length).toBe(postSecondClickInputs.length + postSecondClickSelects.length); + }); + + it('should show previously saved addresses, with prior addresses (primary borrower)', () => { + spyOn(service, 'getPrimaryAddressValues').and.returnValue(mockPrimaryAddress1); + spyOn(service, 'getPrimaryResidenceValues').and.returnValue(mockPrimaryResidence1); + spyOn(service, 'getPriorAddressValues').and.returnValue([mockPriorAddress1, mockPriorAddress2]); + spyOn(service, 'getPriorResidenceValues').and.returnValue([mockPriorResidence1, mockPriorResidence2]); + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.PRIMARY_BORROWER); + spyOn(service, 'individualIsKnownSpouse').and.returnValue(false); + fixture.detectChanges(); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.addressInfoGroup).toBeTruthy(); + expect(component.addressInfoGroup.valid).toBeTruthy(); + expect(component.residenceInfoGroup).toBeTruthy(); + expect(component.residenceInfoGroup.valid).toBeTruthy(); + expect(component.priorAddressInfoGroup).toBeTruthy(); + expect(component.priorAddressInfoGroup.valid).toBeTruthy(); + expect(component.priorResidenceInfoGroup).toBeTruthy(); + expect(component.priorResidenceInfoGroup.valid).toBeTruthy(); + expect(component.mailingInfoGroup).toBeFalsy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.localState.nextDisabled).toEqual(false); + expect(component.addressInfoGroup.value).toEqual(mockPrimaryAddress1); + expect(component.residenceInfoGroup.value).toEqual(mockPrimaryResidence1); + expect(component.localState.showPriorAddressCount).toEqual(2); + expect(component.priorAddressInfoGroup.value).toEqual([mockPriorAddress1, mockPriorAddress2]); + expect(component.priorResidenceInfoGroup.value).toEqual([mockPriorResidence1, mockPriorResidence2]); + }); + + it('should show member pre-filled address (primary borrower)', () => { + const mockIndividual = { + 'individualId': 71057, + 'individualType': 'Individual', + 'title': '', + 'firstName': 'JOHN', + 'middleName': 'PAUL', + 'lastName': 'DOE', + 'nameSuffix': 'II', + 'taxId': '999-99-9990', + 'gender': 'M', + 'birthDate': '1970-01-01T00:00:00', + 'branch': 1, + 'driversLicense': '123456789', + 'driversLicenseState': 'WI', + 'ident': '1234567', + 'preferredContact': 'No Preference', + 'maritalStatus': 'Single', + 'isUSCitizen': true, + 'primaryAddressId': 3212176, + 'isEmployee': false, + 'employeeNumber': 0, + 'companyName': '', + 'employerName': 'ABC COMPANY', + 'applicationCountLast90Days': 0, + 'applicationsDeclinedLast90Days': 0, + 'applicationsApprovedLast90Days': 0, + 'address': [{ + 'addressId': 722485, + 'line1': '100 MAIN ST', + 'line2': '', + 'line3': '', + 'city': 'NEW BERLIN', + 'state': 'WI', + 'zipCode': '53151', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 100, + 'occupancy': 'Own', + 'startDate': '1999-03-01T00:00:00', + 'endDate': '2001-03-01T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 3212176, + 'line1': '1400A SCHAUER DR', + 'line2': '', + 'line3': '', + 'city': 'HARTFORD', + 'state': 'WI', + 'zipCode': '53027-1981', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': true, + 'isInternational': false, + 'addressVerification': 1400, + 'occupancy': 'None', + 'startDate': '0001-01-01T00:00:00', + 'endDate': '0001-01-01T00:00:00', + 'uspsVerified': true + }, { + 'addressId': 3222400, + 'line1': '5445 S WESTRIDGE DR', + 'line2': '', + 'line3': '', + 'city': 'NEW BERLIN', + 'state': 'WI', + 'zipCode': '53151-7948', + 'country': 'US', + 'countyName': '', + 'addressType': 'Mailing', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 5445, + 'occupancy': 'None', + 'startDate': '0001-01-01T00:00:00', + 'endDate': '0001-01-01T00:00:00', + 'uspsVerified': false + }], + 'email1': 'scottschmick@landmarkcu.com', + 'email2': '', + 'phone': [{ + 'phoneKey': 'ApexApiWrapper.Apex.MemberServices.PhoneKey', + 'isPrimaryForType': false, + 'isInternational': false, + 'countryCode': '', + 'areaCode': '555', + 'phoneNumber': '555-5555', + 'extension': '0' + }, { + 'phoneKey': 'ApexApiWrapper.Apex.MemberServices.PhoneKey', + 'isPrimaryForType': true, + 'isInternational': false, + 'countryCode': '', + 'areaCode': '262', + 'phoneNumber': '796-1100', + 'extension': '0' + }], + 'status': 'Bankruptcy', + 'isHomeOwner': false, + 'isHeadOfHouseHold': false, + 'language': '' + }; + memberService.transformCoreData(mockIndividual); + spyOn(service, 'getPrimaryResidenceValues').and.returnValue(mockPrimaryResidence2); + fixture.detectChanges(); + expect(component.addressInfoGroup.getRawValue().city).toEqual('HARTFORD'); + expect(component.pageForm.valid).toBeTruthy('Page Form valid'); + expect(component.addressInfoGroup).toBeTruthy('address info group not present'); + expect(component.addressInfoGroup.valid).toBeTruthy('addressInfoGroup invalid'); + expect(component.residenceInfoGroup).toBeTruthy('residence info group not present'); + expect(component.residenceInfoGroup.valid).toBeTruthy('resident info group invalid'); + expect(component.mailingInfoGroup).toBeFalsy('mailing info group is present'); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy('button next is disabled'); + expect(component.localState.nextDisabled).toEqual(false, 'Next is marked Disabled'); + }); + + it('should show previously answered is same address question (known spouse)', () => { + spyOn(service, 'getPrimaryAddressValues').and.returnValue(mockPrimaryAddress1); + spyOn(service, 'getPrimaryResidenceValues').and.returnValue(mockPrimaryResidence1); + spyOn(service, 'getIsSameAddressValues').and.returnValue(mockIsSameAddressSame.isAddressSame); + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.CO_BORROWER); + spyOn(service, 'individualIsKnownSpouse').and.returnValue(true); + fixture.detectChanges(); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.isAddressSameGroup).toBeTruthy(); + expect(component.addressInfoGroup).toBeFalsy(); + expect(component.residenceInfoGroup).toBeFalsy(); + expect(component.mailingInfoGroup).toBeFalsy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.isAddressSameGroup.value).toEqual(mockIsSameAddressSame); + }); + + it('should show previously saved addresses (known spouse)', () => { + spyOn(service, 'getPrimaryAddressValues').and.returnValue(mockPrimaryAddress1); + spyOn(service, 'getPrimaryResidenceValues').and.returnValue(mockPrimaryResidence2); + spyOn(service, 'getIsSameAddressValues').and.returnValue(mockIsSameAddressDiff.isAddressSame); + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.CO_BORROWER); + spyOn(service, 'individualIsKnownSpouse').and.returnValue(true); + fixture.detectChanges(); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.isAddressSameGroup).toBeTruthy(); + expect(component.addressInfoGroup).toBeTruthy(); + expect(component.residenceInfoGroup).toBeTruthy(); + expect(component.mailingInfoGroup).toBeFalsy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.isAddressSameGroup.value).toEqual(mockIsSameAddressDiff); + expect(component.addressInfoGroup.value).toEqual(mockPrimaryAddress1); + expect(component.residenceInfoGroup.value).toEqual(mockPrimaryResidence2); + }); + + it('should show previously saved mailing address (primary borrower)', () => { + spyOn(service, 'getPrimaryAddressValues').and.returnValue(mockPrimaryAddress1); + spyOn(service, 'getMailingAddressValue').and.returnValue(mockMailingAddress1); + spyOn(service, 'getPrimaryResidenceValues').and.returnValue(mockPrimaryResidenceMailing); + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.PRIMARY_BORROWER); + spyOn(service, 'individualIsKnownSpouse').and.returnValue(false); + fixture.detectChanges(); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.addressInfoGroup).toBeTruthy(); + expect(component.residenceInfoGroup).toBeTruthy(); + expect(component.mailingInfoGroup).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.residenceInfoGroup.value).toEqual(mockPrimaryResidenceMailing); + expect(component.mailingInfoGroup.value).toEqual(mockMailingAddress1); + }); + + it('should correctly default fields for current and prior addresses (primary borrower)', () => { + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.PRIMARY_BORROWER); + spyOn(service, 'individualIsKnownSpouse').and.returnValue(false); + fixture.detectChanges(); + + const preClickInputs = fixture.debugElement.queryAll(By.css('input')); + const preClickSelects = fixture.debugElement.queryAll(By.css('select')); + + expect(component.localState.showPriorAddressCount).toEqual(0); + component.residenceInfoGroup.patchValue({ + residenceTimeYears: '1', + residenceTimeMonths: '0' + }); + fixture.detectChanges(); + expect(component.localState.showPriorAddressCount).toEqual(1); + + const postClickInputs = fixture.debugElement.queryAll(By.css('input')); + const postClickSelects = fixture.debugElement.queryAll(By.css('select')); + + expect(preClickInputs.length + preClickSelects.length).toBeLessThan(postClickInputs.length + postClickSelects.length); + + const baseValue = component.residenceInfoGroup.value; + expect(baseValue).toEqual({ + 'isMailingAddressSame': true, + 'residenceMonthlyPayment': null, + 'residenceTimeYears': '1', + 'residenceTimeMonths': '0', + 'residenceStatus': '0' + }); + const defaultValue = component.priorResidenceInfoGroup.at(0).value; + expect(defaultValue).toEqual(service.createDefaultPriorResidenceInfo()); + }); + + it('should submit the USPS-Verified Primary Address Value if the match was saved', () => { + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.PRIMARY_BORROWER); + spyOn(service, 'saveAndNavigate'); + fixture.detectChanges(); + component.addressInfoGroup.patchValue(mockPriorAddress1); + const mockUSPSAddress: AddressInformation = { + addressLine1: mockPriorAddress1.addressLine1.toLocaleUpperCase(), + addressLine2: mockPriorAddress1.addressLine2.toLocaleUpperCase(), + city: mockPriorAddress1.city.toLocaleUpperCase(), + state: mockPriorAddress1.state.toLocaleUpperCase(), + zipcode: mockPriorAddress1.zipcode, + zipcodeExt: '5446' + }; + component.confirmPrimaryAddressInformation(mockUSPSAddress); + const fields = component.generateFieldsFromGroups(); + expect(fields['addressInfo']).toEqual(mockUSPSAddress); + }); + + it('should submit the USPS-Verified Mailing Address Value if the match was saved', () => { + spyOn(service, 'determineBorrowerType').and.returnValue(BorrowerType.PRIMARY_BORROWER); + spyOn(service, 'saveAndNavigate'); + spyOn(service, 'requiresMailingAddress').and.returnValue(true); + fixture.detectChanges(); + component.mailingInfoGroup.patchValue(mockPriorAddress1); + component.residenceInfoGroup.patchValue({isMailingAddressSame: CreditApplicationValues.CHOICE_YES}); + fixture.detectChanges(); + // component. + const mockUSPSAddress: AddressInformation = { + addressLine1: mockPriorAddress1.addressLine1.toLocaleUpperCase(), + addressLine2: mockPriorAddress1.addressLine2.toLocaleUpperCase(), + city: mockPriorAddress1.city.toLocaleUpperCase(), + state: mockPriorAddress1.state.toLocaleUpperCase(), + zipcode: mockPriorAddress1.zipcode, + zipcodeExt: '5446' + }; + component.confirmMailingAddressInformation(mockUSPSAddress); + const fields = component.generateFieldsFromGroups(); + expect(fields['mailingInfo']).toEqual(mockUSPSAddress); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.ts new file mode 100644 index 0000000..9dcc0b1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.component.ts @@ -0,0 +1,343 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { FormArray, FormGroup } from '@angular/forms'; +import { AddressInfoPageService } from './address-info-page.service'; +import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { + AddressCompareInformation, AddressDiffInformation +} from 'src/app/loan-wizard/field-groups/address-compare/address-compare-information'; +import { AddressInformation } from 'src/app/loan-wizard/field-groups/address/address-information'; +import { AddressFormBuilderService } from 'src/app/shared/address-validation/address-form-builder.service'; +import { AddressValidationFormatConverter } from 'src/app/shared/address-validation/address-validation-format-converter'; + +@Component({ + selector: 'lw-address-info-page', + templateUrl: './address-info-page.component.html' +}) +export class AddressInfoPageComponent implements OnInit, OnDestroy { + private addressStatusSubscription: Subscription; + private pageStatusSubscription: Subscription; + private pageValueSubscription: Subscription; + + public localState = { + showPriorAddressCount: 0, + previousDisabled: false, + nextDisabled: true, + borrowerType: BorrowerType.PRIMARY_BORROWER, + borrowerName: 'Applicant', + nextTopic: '', + primaryAddressInformation: null, + mailingAddressInformation: null, + addressVerifyStatus: null, + isSigningSpouse: false, + isHelocRateMod: false + }; + + public savingData = null; + + public isAddressSameGroup: FormGroup; + public addressInfoGroup: FormGroup; + public primaryAddressVerifyInfoGroup: FormGroup; + public mailingAddressVerifyInfoGroup: FormGroup; + + public residenceInfoGroup: FormGroup; + public mailingInfoGroup: FormGroup; + public priorAddressInfoGroup: FormArray; + public priorResidenceInfoGroup: FormArray; + public compareAddressInfoGroup: FormGroup; + + public addressCompareInformation: AddressCompareInformation; + + public mailingInfoGroupName = 'mailingInfo'; + public addressInfoGroupName = 'addressInfo'; + + public COMPARE_ADDRESS_FIELD = 'compareAddress'; + public USER_INPUT_FIELD = 'userInput'; + public ADDRESS_SELECT_FIELD = 'addressSelection'; + + public pageForm: FormGroup; + public isHelocRateMod: boolean; + private readonly FORM_STATE_INVALID = 'INVALID'; + + constructor( + private addressInfoPageService: AddressInfoPageService, + private route: ActivatedRoute) { + } + + public ngOnInit() { + this.localState.borrowerType = this.addressInfoPageService.determineBorrowerType(this.route); + this.pageForm = this.addressInfoPageService.createPageGroup(); + this.pageStatusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + this.determineIsSameAddressShown(); + this.updateLocalState(this.pageForm.value); + } + + public ngOnDestroy() { + if (this.pageStatusSubscription) { + this.pageStatusSubscription.unsubscribe(); + } + if (this.addressStatusSubscription) { + this.addressStatusSubscription.unsubscribe(); + } + if (this.pageValueSubscription) { + this.pageValueSubscription.unsubscribe(); + } + } + + public determineIsSameAddressShown() { + if (this.addressInfoPageService.individualIsKnownSpouse(this.localState.borrowerType)) { + this.addressInfoPageService.addIsAddressSameGroupIfMissing(this.pageForm); + this.isAddressSameGroup = this.pageForm.get(AddressInfoPageService.IS_ADDRESS_SAME_FORM_NAME); + const previousIsSame = this.addressInfoPageService.getIsSameAddressValues(this.localState.borrowerType); + if (typeof previousIsSame === 'string') { + this.isAddressSameGroup.patchValue({isAddressSame: previousIsSame}); + } else { + this.isAddressSameGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_YES}); + } + } + } + + public updateLocalState(modelValue: any): void { + if (this.pageValueSubscription) { + this.pageValueSubscription.unsubscribe(); + } + + if (this.isAddressDifferentFromPreviouslyEntered()) { + this.addAllAddressGroupsIfMissing(); + } else { + this.removeAllAddressGroupsIfPresent(); + } + + this.localState = { + showPriorAddressCount: this.addressInfoPageService + .getRequiredAddressCount(this.pageForm.value.residenceInformation, this.pageForm.value.priorResidenceInformation), + previousDisabled: this.localState.previousDisabled, + nextDisabled: this.localState.nextDisabled, + borrowerType: this.localState.borrowerType, + borrowerName: this.addressInfoPageService.getApplicantName(this.localState.borrowerType), + nextTopic: this.addressInfoPageService.getNextTopicMessage(), + primaryAddressInformation: this.localState.primaryAddressInformation ? + this.localState.primaryAddressInformation : + this.addressInfoPageService.getPrimaryAddressValues(this.localState.borrowerType), + mailingAddressInformation: this.localState.mailingAddressInformation ? + this.localState.mailingAddressInformation : + this.addressInfoPageService.getMailingAddressValue(this.localState.borrowerType), + addressVerifyStatus: this.localState.addressVerifyStatus, + isSigningSpouse: this.addressInfoPageService.individualIsSigningSpouse(this.localState.borrowerType), + isHelocRateMod: this.addressInfoPageService.isHelocRateMod() + }; + + this.updateOptionalMailingForm(); + this.addAdditionalPriorAddressGroups(); + this.pageValueSubscription = this.pageForm.valueChanges.subscribe(() => { + this.updateLocalState(this.pageForm.value); + }); + } + + public isAddressDifferentFromPreviouslyEntered(): boolean { + return (!this.isAddressSameGroup || this.isAddressSameGroup.value.isAddressSame === CreditApplicationValues.CHOICE_NO); + } + + public removeAllAddressGroupsIfPresent() { + this.addressInfoPageService.removeAddressResidenceGroups(this.pageForm); + this.addressInfoGroup = null; + this.primaryAddressVerifyInfoGroup = null; + this.residenceInfoGroup = null; + this.priorAddressInfoGroup = null; + this.priorResidenceInfoGroup = null; + this.mailingInfoGroup = null; + if (this.addressStatusSubscription) { + this.addressStatusSubscription.unsubscribe(); + this.addressCompareInformation = null; + this.compareAddressInfoGroup = null; + } + } + + public addAllAddressGroupsIfMissing() { + if (!this.addressInfoGroup) { + this.addressInfoPageService.addAddressResidenceGroupsIfMissing(this.pageForm); + this.addressInfoGroup = this.pageForm.get(AddressInfoPageService.ADDRESS_FORM_NAME); + this.residenceInfoGroup = this.pageForm.get(AddressInfoPageService.RESIDENCE_FORM_NAME); + this.priorAddressInfoGroup = this.pageForm.get(AddressInfoPageService.PRIOR_ADDRESS_FORM_NAME); + this.priorResidenceInfoGroup = this.pageForm.get(AddressInfoPageService.PRIOR_RESIDENCE_FORM_NAME); + const savedAddressInfoValues = this.addressInfoPageService.getPrimaryAddressValues(this.localState.borrowerType); + if (savedAddressInfoValues) { + this.addressInfoGroup.patchValue(savedAddressInfoValues); + this.localState.primaryAddressInformation = savedAddressInfoValues; + } + this.residenceInfoGroup.patchValue( + this.addressInfoPageService.getPrimaryResidenceValues(this.localState.borrowerType)); + const priorAddressSet = this.addressInfoPageService.getPriorAddressValues(this.localState.borrowerType); + const priorResidenceSet = this.addressInfoPageService.getPriorResidenceValues(this.localState.borrowerType); + if (priorAddressSet && priorAddressSet.length > 0) { + this.pageForm.removeControl(AddressInfoPageService.PRIOR_ADDRESS_FORM_NAME); + this.pageForm.removeControl(AddressInfoPageService.PRIOR_RESIDENCE_FORM_NAME); + // create prior address groups here. + for (let priorLoop = 0; priorLoop < priorAddressSet.length; priorLoop++) { + this.priorAddressInfoGroup.push(this.addressInfoPageService.createAddressInformationGroup()); + this.priorAddressInfoGroup.at(priorLoop).patchValue(priorAddressSet[priorLoop]); + this.priorResidenceInfoGroup.push(this.addressInfoPageService.createResidenceInformationGroup(false)); + this.priorResidenceInfoGroup.at(priorLoop).patchValue(priorResidenceSet[priorLoop]); + } + this.pageForm.addControl(AddressInfoPageService.PRIOR_ADDRESS_FORM_NAME, this.priorAddressInfoGroup); + this.pageForm.addControl(AddressInfoPageService.PRIOR_RESIDENCE_FORM_NAME, this.priorResidenceInfoGroup); + } + } + } + + public confirmPrimaryAddressInformation(addressInformation: AddressInformation) { + if (!this.primaryAddressVerifyInfoGroup) { + AddressFormBuilderService.addAddressCompareGroupIfMissing(this.pageForm, AddressInfoPageService.PRIMARY_ADDRESS_VERIFY_FORM_NAME); + this.primaryAddressVerifyInfoGroup = this.pageForm.get(AddressInfoPageService.PRIMARY_ADDRESS_VERIFY_FORM_NAME); + } + if (!addressInformation) { + this.primaryAddressVerifyInfoGroup.patchValue({addressVerifyStatus: false}); + } else { + this.primaryAddressVerifyInfoGroup.patchValue({addressVerifyStatus: true}); + this.patchPrimaryAddressIfFieldsConfirmedAreDifferent(addressInformation); + } + this.localState.primaryAddressInformation = addressInformation; + } + + public patchPrimaryAddressIfFieldsConfirmedAreDifferent(addressInformation: AddressInformation) { + const addressDiff: AddressDiffInformation = AddressValidationFormatConverter.getAddressValidationDiff( + AddressValidationFormatConverter.makeApiAddress(this.addressInfoGroup.value), + addressInformation + ); + if (!addressDiff.allFieldsSame) { + this.addressInfoGroup.patchValue(addressInformation); + } + } + + public confirmMailingAddressInformation(addressInformation: AddressInformation) { + if (!this.mailingAddressVerifyInfoGroup) { + AddressFormBuilderService.addAddressCompareGroupIfMissing(this.pageForm, AddressInfoPageService.MAILING_ADDRESS_VERIFY_FORM_NAME); + this.mailingAddressVerifyInfoGroup = this.pageForm.get(AddressInfoPageService.MAILING_ADDRESS_VERIFY_FORM_NAME); + } + if (!addressInformation) { + this.mailingAddressVerifyInfoGroup.patchValue({addressVerifyStatus: false}); + } else { + this.mailingAddressVerifyInfoGroup.patchValue({addressVerifyStatus: true}); + this.patchMailingAddressIfFieldsConfirmedAreDifferent(addressInformation); + } + this.localState.mailingAddressInformation = addressInformation; + } + + public patchMailingAddressIfFieldsConfirmedAreDifferent(addressInformation: AddressInformation) { + const addressDiff: AddressDiffInformation = AddressValidationFormatConverter.getAddressValidationDiff( + AddressValidationFormatConverter.makeApiAddress(this.mailingInfoGroup.value), + addressInformation + ); + if (!addressDiff.allFieldsSame) { + this.mailingInfoGroup.patchValue(addressInformation); + } + } + + public updateOptionalMailingForm(): void { + const showMailingAddress = this.addressInfoPageService.requiresMailingAddress(this.pageForm.value.residenceInformation); + if (showMailingAddress && !this.mailingInfoGroup) { + this.mailingInfoGroup = this.addressInfoPageService.createAddressInformationGroup(); + this.pageForm.addControl(AddressInfoPageService.MAIL_FORM_NAME, this.mailingInfoGroup); + const mailingInfo = this.addressInfoPageService.getMailingAddressValue(this.localState.borrowerType); + if (mailingInfo) { + this.mailingInfoGroup.patchValue(mailingInfo); + } + } else if (!showMailingAddress && this.mailingInfoGroup) { + this.pageForm.removeControl(AddressInfoPageService.MAIL_FORM_NAME); + this.pageForm.removeControl(AddressInfoPageService.MAILING_ADDRESS_VERIFY_FORM_NAME); + this.mailingInfoGroup = null; + this.mailingAddressVerifyInfoGroup = null; + } + } + + public addAdditionalPriorAddressGroups(): void { + if (this.priorAddressInfoGroup) { + if (this.priorResidenceInfoGroup.length < this.localState.showPriorAddressCount) { + // It appears to only re-validate the form if we remove and add the control back. Calling updateValudAndValidity does nothing. + this.pageForm.removeControl(AddressInfoPageService.PRIOR_ADDRESS_FORM_NAME); + const priorResidenceGroup = this.addressInfoPageService.createResidenceInformationGroup(false); + priorResidenceGroup.patchValue(this.addressInfoPageService.createDefaultPriorResidenceInfo()); + this.priorResidenceInfoGroup.push(priorResidenceGroup); + this.priorAddressInfoGroup.push(this.addressInfoPageService.createAddressInformationGroup()); + this.pageForm.addControl(AddressInfoPageService.PRIOR_ADDRESS_FORM_NAME, this.priorAddressInfoGroup); + } else if (this.priorAddressInfoGroup.length > this.localState.showPriorAddressCount) { + this.priorResidenceInfoGroup.removeAt(this.priorResidenceInfoGroup.length - 1); + this.priorAddressInfoGroup.removeAt(this.priorAddressInfoGroup.length - 1); + } + } + } + + public formatPriorInfo() { + return { + priorAddressInfo: this.priorAddressInfoGroup.value, + priorResidenceInfo: this.priorResidenceInfoGroup.value + }; + } + + public formatIsAddressSameInfo() { + return { + hasSameAddressAsSpouse: this.isAddressSameGroup.value.isAddressSame + }; + } + + public formatPrimaryCompareAddress() { + return { + addressInfo: this.localState.primaryAddressInformation + }; + } + + public formatMailingCompareAddress() { + return { + mailingInfo: this.localState.mailingAddressInformation + }; + } + + public generateFieldsFromGroups() { + const fields = {}; + if (this.pageForm.get(AddressInfoPageService.IS_ADDRESS_SAME_FORM_NAME)) { + Object.assign(fields, this.formatIsAddressSameInfo()); + } + if (this.pageForm.get(AddressInfoPageService.PRIOR_ADDRESS_FORM_NAME)) { + Object.assign(fields, this.formatPriorInfo()); + } + + if (this.pageForm.get(AddressInfoPageService.PRIMARY_ADDRESS_VERIFY_FORM_NAME)) { + Object.assign(fields, this.formatPrimaryCompareAddress(), {residenceInfo: this.residenceInfoGroup.value}); + } else if (this.addressInfoGroup) { + Object.assign(fields, + { + addressInfo: this.addressInfoGroup.value, + residenceInfo: this.residenceInfoGroup.value + } + ); + } + + if (this.pageForm.get(AddressInfoPageService.MAILING_ADDRESS_VERIFY_FORM_NAME)) { + Object.assign(fields, this.formatMailingCompareAddress()); + } else if (this.pageForm.get(AddressInfoPageService.MAIL_FORM_NAME)) { + Object.assign(fields, {mailingInfo: this.mailingInfoGroup.value}); + } + + return fields; + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + const fields = this.generateFieldsFromGroups(); + this.addressInfoPageService.saveAndNavigate(fields, this.route, this.localState.borrowerType); + this.savingData = { + isActive: true + }; + this.localState.nextDisabled = true; + } + + public commandPrevious() { + this.addressInfoPageService.cancelAndNavigate(this.route); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.service.spec.ts new file mode 100644 index 0000000..aa698ef --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.service.spec.ts @@ -0,0 +1,171 @@ +import { AddressInfoPageService } from './address-info-page.service'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { AddressInformation } from '../../../field-groups/address/address-information'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { ResidenceInformation } from '../../../field-groups/address/residence-information'; +import { BorrowerType } from '../../../../shared/borrower-type'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + + +describe('Address Info Page Service', () => { + + let service: AddressInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let creditApplicationService: CreditApplicationService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(AddressInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + creditApplicationService = TestBed.get(CreditApplicationService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create an empty top level form group regardless of type', () => { + spyOn(service, 'createAddressInformationGroup').and.callThrough(); + spyOn(service, 'createIsAddressSameGroup').and.callThrough(); + service.createPageGroup(); + expect(service.createAddressInformationGroup).not.toHaveBeenCalled(); + expect(service.createIsAddressSameGroup).not.toHaveBeenCalled(); + }); + + it('should create an address info group', () => { + const addressGroup = service.createAddressInformationGroup(); + expect(addressGroup).toBeTruthy(); + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + expect(addressGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should create a residence info group', () => { + const residenceGroup = service.createResidenceInformationGroup(); + expect(residenceGroup).toBeTruthy(); + CommonGroups.RESIDENCE_INFO.fields.forEach((eachField) => { + expect(residenceGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should provide the value for the groups', () => { + const testGroupName = 'testGroup'; + spyOn(applicationWizardService, 'getIndividualGroupValues').and.returnValue(true); + service.getApplicationGroupValues(testGroupName, BorrowerType.PRIMARY_BORROWER); + expect(applicationWizardService.getIndividualGroupValues).toHaveBeenCalledWith(testGroupName, BorrowerType.PRIMARY_BORROWER); + }); + + it('should navigate the application, on successful save', () => { + spyOn(creditApplicationService, 'saveAddressAndResidenceInformation').and.returnValue(of(true)); + spyOn(router, 'navigate'); + spyOn(applicationWizardService, 'getNextRoute').and.returnValue('foo bar'); + const info = {}; + const resInfo = {}; + const activatedRoute = {}; + const fields = {addressInfo: info, mailingInfo: info, priorAddressInfo: [info], residenceInfo: resInfo, priorResidenceInfo: [resInfo]}; + service.saveAndNavigate(fields, activatedRoute, BorrowerType.PRIMARY_BORROWER); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'saveAddressAndResidenceInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + spyOn(applicationWizardService, 'getNextRoute').and.returnValue('foo bar'); + const info = {}; + const resInfo = {}; + const activatedRoute = {}; + const fields = {addressInfo: info, mailingInfo: info, priorAddressInfo: [info], residenceInfo: resInfo, priorResidenceInfo: [resInfo]}; + service.saveAndNavigate(fields, activatedRoute, BorrowerType.PRIMARY_BORROWER); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('cancelling should always navigate', () => { + spyOn(router, 'navigate'); + const activatedRoute = {}; + service.cancelAndNavigate(activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should correctly count months at residence', () => { + expect(service.countMonthsAtResidence(null)).toBeNaN(); + expect(Number.isNaN(service.countMonthsAtResidence({ + residenceTimeYears: '', + residenceTimeMonths: '', + residenceStatus: null, + isMailingAddressSame: true + }))).toEqual(true); + + expect(service.countMonthsAtResidence({ + residenceTimeYears: '1', + residenceTimeMonths: '1', + residenceStatus: null, + isMailingAddressSame: true + })).toEqual(13); + }); + + it('should calculate a required address count', () => { + expect(service.getRequiredAddressCount({ + residenceTimeYears: '1', + residenceTimeMonths: '11', + residenceStatus: null, + isMailingAddressSame: true + })).toEqual(1); + + expect(service.getRequiredAddressCount({ + residenceTimeYears: '', + residenceTimeMonths: '', + residenceStatus: null, + isMailingAddressSame: true + })).toEqual(0); + + expect(service.getRequiredAddressCount({ + residenceTimeYears: '2', + residenceTimeMonths: '0', + residenceStatus: null, + isMailingAddressSame: true + })).toEqual(0); + }); + + it('should not return address information for an incomplete return', () => { + const undefinedStateInformation: AddressInformation = { + addressLine1: '', + city: '', + state: CreditApplicationValues.UNDEFINED, + zipcode: '' + }; + spyOn(applicationWizardService, 'getIndividualGroupValues').and.returnValue(undefinedStateInformation); + expect(service.getPrimaryAddressValues(BorrowerType.PRIMARY_BORROWER)).toBeFalsy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.service.ts new file mode 100644 index 0000000..19fed7a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/address-info-page.service.ts @@ -0,0 +1,266 @@ +import { Injectable } from '@angular/core'; +import { FormGroup, FormBuilder, FormArray, ValidatorFn } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { AddressInformation } from '../../../field-groups/address/address-information'; +import { ResidenceInformation } from '../../../field-groups/address/residence-information'; +import { PageService } from '../../page-service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; +import { FormatService } from 'src/app/shared/pipes/format.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AddressInfoPageService implements PageService { + + public static readonly ADDRESS_FORM_NAME = 'addressInformation'; + public static readonly MAIL_FORM_NAME = 'mailingAddressInformation'; + public static readonly PRIOR_ADDRESS_FORM_NAME = 'priorAddressInformation'; + public static readonly RESIDENCE_FORM_NAME = 'residenceInformation'; + public static readonly PRIOR_RESIDENCE_FORM_NAME = 'priorResidenceInformation'; + public static readonly IS_ADDRESS_SAME_FORM_NAME = 'isAddressSameInformation'; + public static readonly PRIMARY_ADDRESS_VERIFY_FORM_NAME = 'primaryAddressVerifyInformation'; + public static readonly MAILING_ADDRESS_VERIFY_FORM_NAME = 'mailingAddressVerifyInformation'; + + private CURRENT_ROUTE = 'step-3'; + public readonly REQUIRE_PRIOR_MONTHS_TOTAL = 24; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private formatService: FormatService, + private router: Router, + private formBuilder: FormBuilder) { + } + + public createPageGroup(): FormGroup { + return this.formBuilder.group({}); + } + + public addIsAddressSameGroupIfMissing(group: FormGroup) { + const isAddressSameGroup = this.createIsAddressSameGroup(); + group.addControl(AddressInfoPageService.IS_ADDRESS_SAME_FORM_NAME, isAddressSameGroup); + } + + public addAddressResidenceGroupsIfMissing(group: FormGroup) { + if (group.get(AddressInfoPageService.ADDRESS_FORM_NAME)) { + return; + } + const memberGroup = this.createAddressInformationGroup(); + const residenceGroup = this.createResidenceInformationGroup(); + + group.addControl(AddressInfoPageService.ADDRESS_FORM_NAME, memberGroup); + group.addControl(AddressInfoPageService.RESIDENCE_FORM_NAME, residenceGroup); + group.addControl(AddressInfoPageService.PRIOR_ADDRESS_FORM_NAME, this.formBuilder.array([])); + group.addControl(AddressInfoPageService.PRIOR_RESIDENCE_FORM_NAME, this.formBuilder.array([])); + residenceGroup.patchValue({isMailingAddressSame: true}); + } + + public removeAddressResidenceGroups(group: FormGroup) { + if (!group.get(AddressInfoPageService.ADDRESS_FORM_NAME)) { + return; + } + group.removeControl(AddressInfoPageService.ADDRESS_FORM_NAME); + group.removeControl(AddressInfoPageService.RESIDENCE_FORM_NAME); + group.removeControl(AddressInfoPageService.PRIOR_ADDRESS_FORM_NAME); + group.removeControl(AddressInfoPageService.PRIOR_RESIDENCE_FORM_NAME); + group.removeControl(AddressInfoPageService.PRIMARY_ADDRESS_VERIFY_FORM_NAME); + } + + public createIsAddressSameGroup(): FormGroup { + const isAddressSameGroup = this.formBuilder.group({}); + CommonGroups.IS_ADDRESS_SAME_INFO.fields.forEach((eachField) => { + isAddressSameGroup.addControl(eachField.field, this.formBuilder.control('', eachField.validation)); + }); + return isAddressSameGroup; + } + + public createAddressInformationGroup(): FormGroup { + const memberGroup = this.formBuilder.group({}); + + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, this.formBuilder.control('')); + }, this); + + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + memberGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + return memberGroup; + } + + public createResidenceInformationGroup(isPrimaryResidenceInfo: boolean = true): FormGroup { // add logic here + const memberGroup = this.formBuilder.group({}); + + CommonGroups.RESIDENCE_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, this.formBuilder.control('')); + }, this); + + CommonGroups.RESIDENCE_INFO.fields.forEach((eachField) => { + memberGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + if (isPrimaryResidenceInfo) { + CommonGroups.PRIMARY_RESIDENCE_INFO.fields.forEach((eachField) => { + const validators: Array = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + const validatorFn = validationFunction(); + validators.push(validatorFn); + }, this); + } + memberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + } + + return memberGroup; + } + + public getMailingAddressValue(borrowerType: BorrowerType): any { + const mailingInfo = this.getApplicationGroupValues(AddressInfoPageService.MAIL_FORM_NAME, borrowerType); + if (mailingInfo.state) { + return mailingInfo; + } + } + + public getIsSameAddressValues(borrowerType: BorrowerType): any { + return this.getApplicationGroupValues('hasSameAddressAsSpouse', borrowerType); + } + + public getPrimaryAddressValues(borrowerType: BorrowerType): AddressInformation { + const primaryAddress = Object.assign({}, this.getApplicationGroupValues(AddressInfoPageService.ADDRESS_FORM_NAME, borrowerType)); + if (primaryAddress.state === CreditApplicationValues.UNDEFINED) { + primaryAddress.state = ''; + } + if (primaryAddress.state) { + return primaryAddress; + } + } + + public getPriorAddressValues(borrowerType: BorrowerType): Array { + return this.getApplicationGroupValues(AddressInfoPageService.PRIOR_ADDRESS_FORM_NAME, borrowerType); + } + + public getPrimaryResidenceValues(borrowerType: BorrowerType): any { + return this.getApplicationGroupValues(AddressInfoPageService.RESIDENCE_FORM_NAME, borrowerType); + } + + public getPriorResidenceValues(borrowerType: BorrowerType): Array { + return this.getApplicationGroupValues(AddressInfoPageService.PRIOR_RESIDENCE_FORM_NAME, borrowerType); + } + + public getApplicationGroupValues(groupName: string, individual: BorrowerType): any { + return this.applicationWizardService.getIndividualGroupValues(groupName, individual); + } + + public createDefaultPriorResidenceInfo(): any { + return { + 'residenceTimeYears': '', + 'residenceTimeMonths': '0', + 'residenceStatus': '0' + }; + } + + public saveAndNavigate(fields: { + addressInfo?: AddressInformation, + mailingInfo?: AddressInformation, + priorAddressInfo?: Array, + residenceInfo?: ResidenceInformation, + priorResidenceInfo?: Array, + hasSameAddressAsSpouse?: string + }, route: ActivatedRoute, individual: BorrowerType) { + this.CURRENT_ROUTE = this.getRouteName(); + this.creditApplicationService.saveAddressAndResidenceInformation(fields, individual).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public cancelAndNavigate(route: ActivatedRoute) { + const nextRoute = this.applicationWizardService.getPreviousRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + + public requiresMailingAddress(residenceInformation: ResidenceInformation): boolean { + if (!residenceInformation) { + return false; + } + return !residenceInformation.isMailingAddressSame; + } + + public individualIsSigningSpouse(borrowerType: BorrowerType): boolean { + return borrowerType === BorrowerType.PRIMARY_BORROWER_SPOUSE || borrowerType === BorrowerType.CO_BORROWER_SPOUSE; + } + + public individualIsKnownSpouse(borrowerType: BorrowerType): boolean { + if (borrowerType === BorrowerType.PRIMARY_BORROWER_SPOUSE || borrowerType === BorrowerType.CO_BORROWER_SPOUSE) { + return true; + } + return this.applicationWizardService.isCoApplicantSpouse() && borrowerType === BorrowerType.CO_BORROWER; + } + + public getRequiredAddressCount(residenceInformation: ResidenceInformation, priorResidenceInfo: Array = []): number { + let monthCount = this.countMonthsAtResidence(residenceInformation); + if (isNaN(monthCount)) { + return 0; + } + let residenceCount = 0; + let residenceIndex = 0; + while (monthCount < this.REQUIRE_PRIOR_MONTHS_TOTAL && residenceIndex <= priorResidenceInfo.length) { + residenceCount++; + monthCount += this.countMonthsAtResidence(priorResidenceInfo[residenceIndex++]); + } + return residenceCount; + } + + public countMonthsAtResidence(residenceInfo: ResidenceInformation): number { + if (residenceInfo) { + const monthsStr = residenceInfo.residenceTimeMonths; + const yearStr = residenceInfo.residenceTimeYears; // These are temporary while we decide if we'll change the type. + if (monthsStr === '' || yearStr === '') { + return Number.NaN; + } + return parseInt(monthsStr, 10) + (parseInt(yearStr, 10) * 12); + } + return Number.NaN; + } + + public getRouteName(): string { + return this.applicationWizardService.getSimpleRouteName(this.router.url); + } + + public getNextTopicMessage(): string { + this.CURRENT_ROUTE = this.getRouteName(); + return this.applicationWizardService.getNextTopicMessage(this.CURRENT_ROUTE); + } + + public determineBorrowerType(route: ActivatedRoute): BorrowerType { + if (route.snapshot.data[0]) { + return route.snapshot.data[0].individual; + } + return BorrowerType.PRIMARY_BORROWER; + } + + public getApplicantName(borrowerType: BorrowerType): string { + return this.formatService.formatTitleCase( + this.applicationWizardService.getApplicantName(borrowerType) + ); + } + + public isHelocRateMod(): boolean { + return this.creditApplicationService.isHelocRateMod(this.applicationWizardService.getGroupValues('loanType').title); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-address-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-address-info-page.spec.ts new file mode 100644 index 0000000..1b5e61b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-address-info-page.spec.ts @@ -0,0 +1,61 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoBorrowerAddressInfoPage } from './co-borrower-address-info-page'; +import { configureTestSuite } from 'ng-bullet'; + + +describe('CoBorrower Address Info Page', () => { + + let applicationWizardService: ApplicationWizardService; + let page: CoBorrowerAddressInfoPage; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + page = new CoBorrowerAddressInfoPage( + 'PAGETITLES.COMMON.COAPP_ADDRESS_INFO', + 'co-applicant-residential', + [ + CommonGroups.ADDRESS_INFO + ]); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isCoApplicant').and.callThrough().and.returnValue(true); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isCoApplicant).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isCoApplicant').and.callThrough().and.returnValue(false); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isCoApplicant).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-address-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-address-info-page.ts new file mode 100644 index 0000000..e2540fc --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-address-info-page.ts @@ -0,0 +1,14 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { PrimaryAddressInfoPage } from './primary-address-info-page'; +import { BorrowerType } from 'src/app/shared/borrower-type'; + +export class CoBorrowerAddressInfoPage extends PrimaryAddressInfoPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + return applicationWizardService.isCoApplicant(); + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.CO_BORROWER; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-spouse-address-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-spouse-address-info-page.spec.ts new file mode 100644 index 0000000..387f7e0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-spouse-address-info-page.spec.ts @@ -0,0 +1,70 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoBorrowerSpouseAddressInfoPage } from './co-borrower-spouse-address-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; +import { configureTestSuite } from 'ng-bullet'; + + +describe('CoBorrower Spouse Address Info Page', () => { + + let applicationWizardService: ApplicationWizardService; + let page: CoBorrowerSpouseAddressInfoPage; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + page = new CoBorrowerSpouseAddressInfoPage( + 'PAGETITLES.COMMON.COAPPSPOUSE_ADDRESS_INFO', + 'co-applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.FullInfoSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.TattleSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-spouse-address-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-spouse-address-info-page.ts new file mode 100644 index 0000000..80d4bb7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/co-borrower-spouse-address-info-page.ts @@ -0,0 +1,19 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { PrimaryAddressInfoPage } from './primary-address-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; + +export class CoBorrowerSpouseAddressInfoPage extends PrimaryAddressInfoPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + if (applicationWizardService.isApplicantNonSpouse(this.getBorrowerType())) { + return applicationWizardService.getSpouseType(this.getBorrowerType()) === SpouseType.FullInfoSpouse; + } + return false; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.CO_BORROWER; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-address-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-address-info-page.spec.ts new file mode 100644 index 0000000..0e04b51 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-address-info-page.spec.ts @@ -0,0 +1,63 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { PrimaryAddressInfoPage } from './primary-address-info-page'; +import { TranslateModule } from '@ngx-translate/core'; +import { BorrowerType } from '../../../../../shared/borrower-type'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + + +describe('Primary Address Info Page', () => { + + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + let page: PrimaryAddressInfoPage; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + creditApplicationService.setLoanType(LoanTypeSet[0].id); + page = new PrimaryAddressInfoPage('PAGETITLES.COMMON.ADDRESS_INFO', 'step-3', + [ + CommonGroups.ADDRESS_INFO + ]); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should always return primary borrower type', () => { + spyOn(page, 'getBorrowerType').and.callThrough(); + const borrowerType = page.getBorrowerType(); + expect(page.getBorrowerType).toHaveBeenCalled(); + expect(borrowerType).toBe(BorrowerType.PRIMARY_BORROWER); + }); + + it('should correctly determine if page is valid ( default invalid )', () => { + spyOn(page, 'isValidPage').and.callThrough(); + const errors = page.isValidPage(applicationWizardService, creditApplicationService); + expect(page.isValidPage).toHaveBeenCalled(); + expect(errors.length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-address-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-address-info-page.ts new file mode 100644 index 0000000..221f33a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-address-info-page.ts @@ -0,0 +1,33 @@ +import { ApplicationPage } from '../../../../application-page'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { AddressInformation } from '../../../../field-groups/address/address-information'; +import { ResidenceInformation } from '../../../../field-groups/address/residence-information'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + +export class PrimaryAddressInfoPage extends ApplicationPage { + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + let value; + const errors = []; + const borrowerType = this.getBorrowerType(); + const addressInfo: [AddressInformation | ResidenceInformation] = applicationWizardService + .getIndividualGroupValues('addressInformation', BorrowerType.PRIMARY_BORROWER); + Object.assign(addressInfo, + applicationWizardService.getIndividualGroupValues('residenceInformation', borrowerType) + ); + this.fieldgroup.forEach((pageFieldGroup) => { + pageFieldGroup.fields.forEach((pageField) => { + value = addressInfo[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }, this); + }, this); + return errors; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.PRIMARY_BORROWER; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-spouse-address-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-spouse-address-info-page.spec.ts new file mode 100644 index 0000000..2d5cea4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-spouse-address-info-page.spec.ts @@ -0,0 +1,69 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { PrimarySpouseAddressInfoPage } from './primary-spouse-address-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; +import { configureTestSuite } from 'ng-bullet'; + + +describe('Primary Spouse Address Info Page', () => { + + let applicationWizardService: ApplicationWizardService; + let page: PrimarySpouseAddressInfoPage; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + page = new PrimarySpouseAddressInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_ADDRESS_INFO', + 'applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.FullInfoSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.TattleSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-spouse-address-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-spouse-address-info-page.ts new file mode 100644 index 0000000..c03c3e5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/address/pages/primary-spouse-address-info-page.ts @@ -0,0 +1,19 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { PrimaryAddressInfoPage } from './primary-address-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; + +export class PrimarySpouseAddressInfoPage extends PrimaryAddressInfoPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + if (applicationWizardService.isApplicantNonSpouse(this.getBorrowerType())) { + return applicationWizardService.getSpouseType(this.getBorrowerType()) === SpouseType.FullInfoSpouse; + } + return false; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.PRIMARY_BORROWER; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.html new file mode 100644 index 0000000..ee13cad --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.html @@ -0,0 +1,20 @@ +
+
+

HEADINGS.DECISION

+
+
+
+ + + + +
+
+ Loan Id: {{loanId}} +
+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.scss new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.scss @@ -0,0 +1 @@ + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.spec.ts new file mode 100644 index 0000000..977ae71 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.spec.ts @@ -0,0 +1,52 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { DecisionPageComponent } from './decision-page.component'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { DecisionPageService } from './decision-page.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { ConfigurationService } from '../../../../shared/configuration/configuration.service'; +import { MockConfigurationService } from '../../../../testing/shared-mocks/configuration.mock.service'; +import { DecisionHeroComponent } from '../../../../layout/decision-hero/decision-hero.component'; +import { configureTestSuite } from 'ng-bullet'; + +describe('DecisionPageComponent', () => { + + let component: DecisionPageComponent; + let fixture: ComponentFixture; + let decisionPageService: DecisionPageService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + DecisionPageComponent, + DecisionHeroComponent + ], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService}, + {provide: ConfigurationService, useValue: MockConfigurationService} + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DecisionPageComponent); + component = fixture.componentInstance; + decisionPageService = TestBed.get(DecisionPageService); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + spyOn(component, 'updateLocalState').and.returnValue(null); + fixture.detectChanges(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.ts new file mode 100644 index 0000000..e47614e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.component.ts @@ -0,0 +1,46 @@ +import { Component, OnInit } from '@angular/core'; +import { DecisionPageService } from './decision-page.service'; +import { ConfigurationService } from '../../../../shared/configuration/configuration.service'; +import { HeroType } from '../../../../layout/decision-hero/decision-hero.component'; +import { environment } from 'src/environments/environment'; +import { Stipulation } from '../../../../shared/decision/decision-status'; + +@Component({ + selector: 'lw-decision-page', + templateUrl: './decision-page.component.html', + styleUrls: ['./decision-page.component.scss'] +}) +export class DecisionPageComponent implements OnInit { + + public localState = { + firstName: '', + phoneNumber: '', + heroType: HeroType.SUCCESS, + showLoanId: false, + decisionKey: 'TEXT' + }; + public stipulations: Array = []; + public loanId: String; + + constructor( + private decisionPageService: DecisionPageService, + private configurationService: ConfigurationService + ) { } + + public ngOnInit() { + this.updateLocalState(); + } + + public updateLocalState() { + const creditApplication = this.decisionPageService.getCreditApplication(); + this.localState.firstName = creditApplication.fullInfoBorrowers[0].personalInformation.firstName; + this.localState.decisionKey = creditApplication.loanType.decisionReviewKey; + this.localState.phoneNumber = this.configurationService.getConfigurationByField('contact')['HelpPhone']; + this.localState.heroType = this.decisionPageService.determineDecisionHeroType(); + if (environment.displayLoanId) { + this.localState.showLoanId = environment.displayLoanId; + this.loanId = creditApplication.loanId; + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.service.spec.ts new file mode 100644 index 0000000..2b76788 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.service.spec.ts @@ -0,0 +1,93 @@ +import { Router } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { DecisionPageService } from './decision-page.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { CreditApplication } from '../../../../shared/credit-application'; +import { HeroType } from '../../../../layout/decision-hero/decision-hero.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { DecisionResult, DecisionState, DecisionStatus } from '../../../../shared/decision/decision-status'; +import { DecisionService } from '../../../../shared/decision/decision.service'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +describe('Decision Page Service', () => { + + let service: DecisionPageService; + let creditApplicationService: CreditApplicationService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let decisionService: DecisionService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(DecisionPageService); + creditApplicationService = TestBed.get(CreditApplicationService); + creditApplicationService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + decisionService = TestBed.get(DecisionService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should determine an decision hero type when there is an error', () => { + expect(service).toBeTruthy(); + const application = new CreditApplication(LoanTypeSet[0]); + spyOn(service, 'getDecision') + .and.returnValue(decisionService.createErrorDecisionResult(application.loanType.shouldDecision, DecisionState.ErrorDecide)); + const type = service.determineDecisionHeroType(); + expect(service.getDecision).toHaveBeenCalled(); + expect(type).toEqual(HeroType.ERROR_DECISION); + }); + + it('should determine an decision hero type when there is no decision', () => { + spyOn(service, 'getDecision').and.returnValue(decisionService.createEmptyDecisionResult(true)); + const type = service.determineDecisionHeroType(); + expect(service.getDecision).toHaveBeenCalled(); + expect(type).toEqual(HeroType.REVIEW); + }); + + it('should determine an decision hero type when there is a ready decision', () => { + expect(service).toBeTruthy(); + const resultReadyEmpty = {result: DecisionResult.DecisionStatus_None, state: DecisionState.Ready, stipulations: []}; + spyOn(service, 'getDecision').and.returnValue(resultReadyEmpty); + const type = service.determineDecisionHeroType(); + expect(service.getDecision).toHaveBeenCalled(); + expect(type).toEqual(HeroType.REVIEW); + }); + + it('should determine an decision hero type when there is a ready decision (2)', () => { + expect(service).toBeTruthy(); + const resultReadySuccess = { + result: DecisionResult.DecisionStatus_Approved, + state: DecisionState.Ready, stipulations: [] + }; + spyOn(service, 'getDecision').and.returnValue(resultReadySuccess); + const type = service.determineDecisionHeroType(); + expect(service.getDecision).toHaveBeenCalled(); + expect(type).toEqual(HeroType.SUCCESS); + + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.service.ts new file mode 100644 index 0000000..2bc2d48 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { CreditApplication } from '../../../../shared/credit-application'; +import { HeroType } from '../../../../layout/decision-hero/decision-hero.component'; +import { DecisionResult, DecisionState, DecisionStatus } from '../../../../shared/decision/decision-status'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +@Injectable({ + providedIn: 'root' +}) +export class DecisionPageService { + + constructor( + private creditApplicationService: CreditApplicationService) { + } + + public getCreditApplication(): CreditApplication { + return this.creditApplicationService.getFullCreditApplication(); + } + + public getDecision(): DecisionStatus { + return this.getCreditApplication().decision; + } + + public determineDecisionHeroType(): HeroType { + const decision = this.getDecision(); + if (!decision || decision.state === DecisionState.ErrorSubmit) { + return HeroType.ERROR_SUBMIT; + } else if (decision.state === DecisionState.ErrorDecide) { + return HeroType.ERROR_DECISION; + } else if (decision.state === DecisionState.NotStarted || decision.state === DecisionState.Requested) { + return HeroType.REVIEW; + } else if (decision.state === DecisionState.Ready) { + if (decision.result === DecisionResult.DecisionStatus_Approved) { + return HeroType.SUCCESS; + } else if (decision.result === DecisionResult.DecisionStatus_Declined) { + return HeroType.DENIED; + } else { + return HeroType.REVIEW; + } + } + return HeroType.ERROR_SUBMIT; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.spec.ts new file mode 100644 index 0000000..65f7d6c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.spec.ts @@ -0,0 +1,53 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { DecisionPage } from './decision-page'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +describe('Decision Page', () => { + + let page: DecisionPage; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new DecisionPage('PAGETITLES.CC.INTRO', 'decision', []); + + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should be invalid if there is no credit application in state', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(null); + const isValid = page.isValidPage(applicationWizardService, creditApplicationService); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + expect(isValid.length).toBeGreaterThan(0); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.ts new file mode 100644 index 0000000..72149aa --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/decision/decision-page.ts @@ -0,0 +1,15 @@ +import { ApplicationPage } from '../../../application-page'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +export class DecisionPage extends ApplicationPage { + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + if (applicationWizardService.getFullCreditApplication()) { + return []; + } else { + return ['ERROR']; + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.html new file mode 100644 index 0000000..be31428 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.html @@ -0,0 +1,49 @@ +
+
+
+

+

+
+
COPY.EMPLOYMENT.{{localState.borrowerType}}
+
+
+ + + + + + + + + + + + + +
+
+ + {{ isCCApplication ? "COPY.EMPLOYMENT.OTHER_INCOME_BSA_CC" : "COPY.EMPLOYMENT.OTHER_INCOME_BSA" }} + +
+
+ + + + + + +
+
Click NEXT if you have no income
+
+ + +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.spec.ts new file mode 100644 index 0000000..76b0279 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.spec.ts @@ -0,0 +1,528 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { EmploymentInfoPageComponent } from './employment-info-page.component'; +import { EmploymentInfoPageService } from './employment-info-page.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { EmploymentInformation } from '../../../field-groups/employment/employment-information'; +import { OtherIncomeFlagFormComponent } from '../../../field-groups/employment/financial/other-income-flag-form.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { AlimonyFlagFormComponent } from '../../../field-groups/employment/financial/alimony-flag-form.component'; +import { EmploymentInfoFormComponent } from '../../../field-groups/employment/employment-info-form.component'; +import { AlimonyInfoFormComponent } from '../../../field-groups/employment/alimony-info-form.component'; +import { OtherIncomeInfoFormComponent } from '../../../field-groups/employment/other-income-info-form.component'; +import { EmploymentFlagFormComponent } from '../../../field-groups/employment/financial/employment-flag-form.component'; +import { AlimonyInformation } from '../../../field-groups/employment/alimony-information'; +import { IncomeInformation } from '../../../field-groups/employment/income-information'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { of } from 'rxjs'; +import { configureTestSuite } from 'ng-bullet'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { DefaultSettings } from 'src/app/shared/configuration/default-settings.mock'; +import { UiSettings } from 'src/app/shared/configuration/ui-settings'; +import { MockConfigurationService } from 'src/app/testing/shared-mocks/configuration.mock.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; + +describe('EmploymentInfoPageComponent', () => { + let component: EmploymentInfoPageComponent; + let fixture: ComponentFixture; + let service: EmploymentInfoPageService; + let wizardService: ApplicationWizardService; + let configService: ConfigurationService; + const otherIncome = {amount: 1000, incomeName: 'tree', frequency: '', files: [], isUploading: ''}; + const otherIncomeTwo = {amount: 2000, incomeName: 'bush', frequency: '', files: [], isUploading: ''}; + const otherIncomeThree = {amount: 3000, incomeName: 'leaf', frequency: '', files: [], isUploading: ''}; + const employmentInformation = { + employmentLengthMonths: 2, + employerName: 'test', + employmentLengthYears: 1, + employmentType: 'PartTime', + jobTitle: 'tester', + salary: 100, + schedule: 'Hourly', + weeklyHours: 25, + workPhoneNumber: '', + isUploading: '', + files: [] + }; + const employmentInformationTwo = { + employmentLengthMonths: 4, + employerName: 'test', + employmentLengthYears: 3, + employmentType: 'PartTime', + jobTitle: 'driver', + salary: 10, + schedule: 'Hourly', + weeklyHours: 5, + workPhoneNumber: '', + isUploading: '', + files: [] + }; + const employmentInformationThree = { + employmentLengthMonths: 3, + employerName: 'test', + employmentLengthYears: 2, + employmentType: 'PartTime', + jobTitle: 'planner', + salary: 15, + schedule: 'Hourly', + weeklyHours: 10, + workPhoneNumber: '', + isUploading: '', + files: [] + }; + const alimony = {childSupportAmount: 1000}; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + EmploymentInfoPageComponent, + EmploymentFlagFormComponent, + EmploymentInfoFormComponent, + OtherIncomeFlagFormComponent, + OtherIncomeInfoFormComponent, + AlimonyFlagFormComponent, + AlimonyInfoFormComponent + ], + providers: [ + {provide: ConfigurationService, useValue: MockConfigurationService}, + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(EmploymentInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(EmploymentInfoPageService); + configService = TestBed.get(MockConfigurationService); + wizardService = TestBed.get(ApplicationWizardService); + configService.saveConfiguration(defaultSettings); + spyOn(service, 'getNextTopicMessage').and.callThrough().and.returnValue('foo bar'); + wizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.pageForm).toBeTruthy(); + expect(component.financialInfoGroup).toBeTruthy(); + expect(component.employmentIncomeInfoArray).toBeTruthy(); + expect(component.employmentIncomeInfoArray.length).toEqual(0); + expect(component.otherIncomeInfoArray.length).toEqual(0); + expect(component.alimonyInfoGroup).toBeFalsy(); + expect(component.otherIncomeInfoArray).toBeTruthy(); + const buttonAlimony = fixture.debugElement.query(By.css('#isPayingChildSupport')).nativeElement; + expect(buttonAlimony).toBeTruthy(); + const buttonOtherIncome = fixture.debugElement.query(By.css('#hasOtherIncome0')).nativeElement; + expect(buttonOtherIncome).toBeTruthy(); + const buttonEmploymentIncome = fixture.debugElement.query(By.css('#hasEmploymentIncome0')).nativeElement; + expect(buttonEmploymentIncome).toBeTruthy(); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + expect(component.commandPrevious).not.toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + fixture.detectChanges(); + spyOn(service, 'saveAndNavigate'); + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + spyOn(service, 'cancelAndNavigate'); + component.commandPrevious(); + expect(service.cancelAndNavigate).toHaveBeenCalled(); + }); + + it('should bind the previous button to the correct command function', () => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonPrevious')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).not.toHaveBeenCalled(); + expect(component.commandPrevious).toHaveBeenCalled(); + }); + }); + + it('should have an initially valid page, with only a form group for choosing financials', () => { + fixture.detectChanges(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.pageForm).toBeTruthy(); + expect(component.financialInfoGroup).toBeTruthy(); + expect(component.employmentIncomeInfoArray).toBeTruthy(); + expect(component.otherIncomeInfoArray).toBeTruthy(); + expect(component.employmentIncomeInfoArray.length).toEqual(0); + expect(component.otherIncomeInfoArray.length).toEqual(0); + expect(component.alimonyInfoGroup).toBeFalsy(); + }); + + it('should show and provide an alimony group when selected.', () => { + fixture.detectChanges(); + const buttonAlimony = fixture.debugElement.query(By.css('#isPayingChildSupport')).nativeElement; + buttonAlimony.click(); + fixture.detectChanges(); + expect(component.pageForm.valid).toBeFalsy(); + expect(component.employmentIncomeInfoArray).toBeTruthy(); + expect(component.otherIncomeInfoArray).toBeTruthy(); + expect(component.employmentIncomeInfoArray.length).toEqual(0); + expect(component.otherIncomeInfoArray.length).toEqual(0); + expect(component.alimonyInfoGroup).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeTruthy(); + buttonAlimony.click(); + fixture.detectChanges(); + expect(component.alimonyInfoGroup).toBeFalsy(); + expect(buttonNext.disabled).toBeFalsy(); + }); + + it('should determine an expected other income count', () => { + const financialValues = { + 'hasOtherIncome0': false, + 'hasOtherIncome1': false, + 'hasOtherIncome2': false + }; + expect(component.determineExpectedOtherIncomeCount(undefined)).toEqual(0); + expect(component.determineExpectedOtherIncomeCount(null)).toEqual(0); + expect(component.determineExpectedOtherIncomeCount(financialValues)).toEqual(0); + financialValues.hasOtherIncome0 = true; + expect(component.determineExpectedOtherIncomeCount(financialValues)).toEqual(1); + financialValues.hasOtherIncome0 = false; + financialValues.hasOtherIncome1 = true; + financialValues.hasOtherIncome2 = true; + expect(component.determineExpectedOtherIncomeCount(financialValues)).toEqual(2); + }); + + it('should determine an expected other income removal index', () => { + const financialValues = { + 'hasOtherIncome0': false, + 'hasOtherIncome1': false, + 'hasOtherIncome2': false + }; + + financialValues.hasOtherIncome0 = true; + financialValues.hasOtherIncome1 = false; + financialValues.hasOtherIncome2 = true; + expect(component.determineOtherIncomeRemoveIndex(financialValues)).toEqual(1); + financialValues.hasOtherIncome0 = true; + financialValues.hasOtherIncome1 = true; + financialValues.hasOtherIncome2 = false; + expect(component.determineOtherIncomeRemoveIndex(financialValues)).toEqual(2); + financialValues.hasOtherIncome0 = false; + financialValues.hasOtherIncome1 = true; + financialValues.hasOtherIncome2 = true; + expect(component.determineOtherIncomeRemoveIndex(financialValues)).toEqual(0); + }); + + it('should determine an expected employment income removal index', () => { + const financialValues = { + 'hasEmploymentIncome0': false, + 'hasEmploymentIncome1': false, + 'hasEmploymentIncome2': false + }; + + financialValues.hasEmploymentIncome0 = true; + financialValues.hasEmploymentIncome1 = false; + financialValues.hasEmploymentIncome2 = true; + expect(component.determineEmploymentIncomeRemoveIndex(financialValues)).toEqual(1); + financialValues.hasEmploymentIncome0 = true; + financialValues.hasEmploymentIncome1 = true; + financialValues.hasEmploymentIncome2 = false; + expect(component.determineEmploymentIncomeRemoveIndex(financialValues)).toEqual(2); + financialValues.hasEmploymentIncome0 = false; + financialValues.hasEmploymentIncome1 = true; + financialValues.hasEmploymentIncome2 = true; + expect(component.determineEmploymentIncomeRemoveIndex(financialValues)).toEqual(0); + }); + + it('should show and provide an other income group when selected.', () => { + fixture.detectChanges(); + const buttonOtherIncome0 = fixture.debugElement.query(By.css('#hasOtherIncome0')).nativeElement; + buttonOtherIncome0.click(); + fixture.detectChanges(); + expect(component.financialInfoGroup.value.hasOtherIncome0).toBeTruthy(); + expect(component.determineExpectedOtherIncomeCount(component.financialInfoGroup.value)).toEqual(1); + expect(component.pageForm.valid).toBeFalsy(); + expect(component.employmentIncomeInfoArray).toBeTruthy(); + expect(component.otherIncomeInfoArray).toBeTruthy(); + expect(component.employmentIncomeInfoArray.length).toEqual(0); + expect(component.otherIncomeInfoArray.length).toEqual(1); + expect(component.alimonyInfoGroup).toBeFalsy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeTruthy(); + const buttonOtherIncome1 = fixture.debugElement.query(By.css('#hasOtherIncome1')).nativeElement; + buttonOtherIncome1.click(); + fixture.detectChanges(); + expect(component.otherIncomeInfoArray.length).toEqual(2); + const buttonOtherIncome2 = fixture.debugElement.query(By.css('#hasOtherIncome2')).nativeElement; + buttonOtherIncome2.click(); + fixture.detectChanges(); + expect(component.otherIncomeInfoArray.length).toEqual(3); + }); + + it('should hide and remove an other income group when de-selected.', () => { + fixture.detectChanges(); + const buttonOtherIncome = fixture.debugElement.query(By.css('#hasOtherIncome0')).nativeElement; + buttonOtherIncome.click(); + fixture.detectChanges(); + expect(component.otherIncomeInfoArray.length).toEqual(1); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeTruthy(); + const buttonOtherIncomeAgain = fixture.debugElement.query(By.css('#hasOtherIncome0')).nativeElement; + buttonOtherIncomeAgain.click(); + fixture.detectChanges(); + expect(component.otherIncomeInfoArray).toBeTruthy(); + expect(component.otherIncomeInfoArray.length).toEqual(0); + expect(buttonNext.disabled).toBeFalsy(); + }); + + it('should hide and SHIFT the other income groups when de-selected.', () => { + fixture.detectChanges(); + const buttonOtherIncome0 = fixture.debugElement.query(By.css('#hasOtherIncome0')).nativeElement; + buttonOtherIncome0.click(); + fixture.detectChanges(); + let buttonOtherIncome1 = fixture.debugElement.query(By.css('#hasOtherIncome1')).nativeElement; + buttonOtherIncome1.click(); + fixture.detectChanges(); + const buttonOtherIncome2 = fixture.debugElement.query(By.css('#hasOtherIncome2')).nativeElement; + buttonOtherIncome2.click(); + fixture.detectChanges(); + expect(component.pageForm.valid).toBeFalsy(); + expect(component.otherIncomeInfoArray).toBeTruthy(); + expect(component.otherIncomeInfoArray.length).toEqual(3); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeTruthy(); + buttonOtherIncome1 = fixture.debugElement.query(By.css('#hasOtherIncome1')).nativeElement; + buttonOtherIncome1.click(); + fixture.detectChanges(); + expect(component.otherIncomeInfoArray.length).toEqual(2); + expect(component.financialInfoGroup.value.hasOtherIncome0).toEqual(true); + expect(component.financialInfoGroup.value.hasOtherIncome1).toEqual(true); + expect(component.financialInfoGroup.value.hasOtherIncome2).toEqual(false); + }); + + it('should show and provide an employment income group when selected.', () => { + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + fixture.detectChanges(); + let buttonEmploymentIncome = fixture.debugElement.query(By.css('#hasEmploymentIncome0')).nativeElement; + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + buttonEmploymentIncome.click(); + fixture.detectChanges(); + expect(component.employmentIncomeInfoArray).toBeTruthy(); + expect(component.employmentIncomeInfoArray.length).toEqual(1); + expect(component.employmentIncomeInfoArray.value[0].employmentLengthMonths).toEqual('0'); // should be defaulted + expect(component.otherIncomeInfoArray).toBeTruthy(); + expect(component.alimonyInfoGroup).toBeFalsy(); + expect(buttonNext.disabled).toBeTruthy(); + buttonEmploymentIncome = fixture.debugElement.query(By.css('#hasEmploymentIncome1')).nativeElement; + buttonEmploymentIncome.click(); + fixture.detectChanges(); + expect(component.employmentIncomeInfoArray.length).toEqual(2); + buttonEmploymentIncome = fixture.debugElement.query(By.css('#hasEmploymentIncome2')).nativeElement; + buttonEmploymentIncome.click(); + fixture.detectChanges(); + expect(component.employmentIncomeInfoArray.length).toEqual(3); + }); + + it('should hide and remove an employment income group when de-selected.', () => { + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + fixture.detectChanges(); + let buttonEmploymentIncome = fixture.debugElement.query(By.css('#hasEmploymentIncome0')).nativeElement; + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + buttonEmploymentIncome.click(); + fixture.detectChanges(); + expect(component.employmentIncomeInfoArray.length).toEqual(1); + buttonEmploymentIncome = fixture.debugElement.query(By.css('#hasEmploymentIncome0')).nativeElement; + buttonEmploymentIncome.click(); + fixture.detectChanges(); + expect(component.employmentIncomeInfoArray.length).toEqual(0); + expect(buttonNext.disabled).toBeFalsy(); + }); + + it('should hide and SHIFT employment income groups when de-selected.', () => { + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + fixture.detectChanges(); + const buttonEmploymentIncome0 = fixture.debugElement.query(By.css('#hasEmploymentIncome0')).nativeElement; + buttonEmploymentIncome0.click(); + fixture.detectChanges(); + let buttonEmploymentIncome1 = fixture.debugElement.query(By.css('#hasEmploymentIncome1')).nativeElement; + buttonEmploymentIncome1.click(); + fixture.detectChanges(); + const buttonEmploymentIncome2 = fixture.debugElement.query(By.css('#hasEmploymentIncome2')).nativeElement; + buttonEmploymentIncome2.click(); + fixture.detectChanges(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeTruthy(); + expect(component.employmentIncomeInfoArray.length).toEqual(3); + buttonEmploymentIncome1 = fixture.debugElement.query(By.css('#hasEmploymentIncome1')).nativeElement; + buttonEmploymentIncome1.click(); + fixture.detectChanges(); + expect(component.employmentIncomeInfoArray.length).toEqual(2); + expect(component.financialInfoGroup.value.hasEmploymentIncome0).toEqual(true); + expect(component.financialInfoGroup.value.hasEmploymentIncome1).toEqual(true); + expect(component.financialInfoGroup.value.hasEmploymentIncome2).toEqual(false); + }); + + it('should show and provide a "weekly hours" field when hourly and not full time ', () => { + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + fixture.detectChanges(); + const buttonEmploymentIncome = fixture.debugElement.query(By.css('#hasEmploymentIncome0')).nativeElement; + buttonEmploymentIncome.click(); + fixture.detectChanges(); + + const form = component.employmentIncomeInfoArray.at(0); + expect(form.get('weeklyHours')).toBeFalsy(); + form.patchValue({ 'schedule': 'Hourly', 'employmentType': 'FullTime' }); + fixture.detectChanges(); + expect(form.get('weeklyHours')).toBeFalsy(); + form.patchValue({ 'schedule': 'Hourly', 'employmentType': 'PartTime' }); + fixture.detectChanges(); + expect(form.get('weeklyHours')).toBeTruthy(); + form.patchValue({ 'schedule': 'Hourly', 'employmentType': 'FullTime' }); + fixture.detectChanges(); + expect(form.get('weeklyHours')).toBeFalsy(); + }); + + it('should render existing alimony data', () => { + spyOn(service, 'getAlimonyValue').and.returnValue(alimony); + fixture.detectChanges(); + expect(service.getAlimonyValue).toHaveBeenCalledTimes(2); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.alimonyInfoGroup).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.alimonyInfoGroup.value).toEqual(alimony); + }); + + it('should render existing other income data', () => { + spyOn(service, 'getOtherIncomeValue').and.returnValue([otherIncome]); + fixture.detectChanges(); + // expect(service.getOtherIncomeValue).toHaveBeenCalledTimes(2); // initial, fill + expect(component.pageForm.valid).toBeTruthy(); + expect(component.otherIncomeInfoArray).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.otherIncomeInfoArray.value).toEqual([otherIncome]); + }); + + it('should render up to three existing other income data', () => { + spyOn(service, 'getOtherIncomeValue').and.returnValue([otherIncome, otherIncomeTwo, otherIncomeThree]); + fixture.detectChanges(); + // expect(service.getOtherIncomeValue).toHaveBeenCalledTimes(2); // initial, fill + expect(component.pageForm.valid).toBeTruthy(); + expect(component.otherIncomeInfoArray).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.otherIncomeInfoArray.value).toEqual([otherIncome, otherIncomeTwo, otherIncomeThree]); + }); + + it('should render existing employment income data', () => { + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + spyOn(service, 'getEmploymentIncomeValue').and.returnValue([employmentInformation]); + fixture.detectChanges(); + expect(service.getEmploymentIncomeValue).toHaveBeenCalledTimes(3); // initial, fill, weeklyHours + expect(component.pageForm.valid).toBeTruthy(); + expect(component.employmentIncomeInfoArray).toBeTruthy(); + expect(component.employmentIncomeInfoArray.length).toEqual(1); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.employmentIncomeInfoArray.value).toEqual([employmentInformation]); + }); + + it('should render up to three existing employment income data', () => { + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + spyOn(service, 'getEmploymentIncomeValue').and + .returnValue([employmentInformation, employmentInformationTwo, employmentInformationThree]); + fixture.detectChanges(); + expect(service.getEmploymentIncomeValue).toHaveBeenCalledTimes(7); // initial, fill, weeklyHours + expect(component.pageForm.valid).toBeTruthy(); + expect(component.employmentIncomeInfoArray).toBeTruthy(); + expect(component.employmentIncomeInfoArray.length).toEqual(3); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.employmentIncomeInfoArray.value) + .toEqual([employmentInformation, employmentInformationTwo, employmentInformationThree]); + }); + + it('should remove the correct data when shifting employer data after de-select', () => { + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + spyOn(service, 'getEmploymentIncomeValue').and + .returnValue([employmentInformation, employmentInformationTwo, employmentInformationThree]); + fixture.detectChanges(); + expect(service.getEmploymentIncomeValue).toHaveBeenCalledTimes(7); // initial, fill, weeklyHours + expect(component.pageForm.valid).toBeTruthy(); + expect(component.employmentIncomeInfoArray).toBeTruthy(); + expect(component.employmentIncomeInfoArray.length).toEqual(3); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.employmentIncomeInfoArray.value) + .toEqual([employmentInformation, employmentInformationTwo, employmentInformationThree]); + const buttonEmploymentIncome1 = fixture.debugElement.query(By.css('#hasEmploymentIncome1')).nativeElement; + buttonEmploymentIncome1.click(); + fixture.detectChanges(); + expect(component.employmentIncomeInfoArray.length).toEqual(2); + expect(component.financialInfoGroup.value.hasEmploymentIncome0).toEqual(true); + expect(component.financialInfoGroup.value.hasEmploymentIncome1).toEqual(true); + expect(component.financialInfoGroup.value.hasEmploymentIncome2).toEqual(false); + expect(component.employmentIncomeInfoArray.value) + .toEqual([employmentInformation, employmentInformationThree]); + }); + + it('should accurately submit all data on NEXT', () => { + spyOn(service, 'getAlimonyValue').and.returnValue(alimony); + spyOn(service, 'getOtherIncomeValue').and.returnValue([otherIncome]); + spyOn(service, 'getEmploymentIncomeValue').and.returnValue([employmentInformation]); + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + + fixture.detectChanges(); + expect(component.pageForm.valid).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + spyOn(service, 'saveAndNavigate').and.returnValue(true); + buttonNext.click(); + + expect(service.saveAndNavigate).toHaveBeenCalledWith( + alimony, + [employmentInformation], + [otherIncome], + component.route, 'PRIMARY_BORROWER'); + }); + + it('should accurately submit an empty form on next', () => { + fixture.detectChanges(); + expect(component.pageForm.valid).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + spyOn(service, 'saveAndNavigate').and.returnValue(of(true)); + buttonNext.click(); + expect(service.saveAndNavigate).toHaveBeenCalledWith( + null, + [], + [], component.route, 'PRIMARY_BORROWER'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.ts new file mode 100644 index 0000000..e5fb79c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.component.ts @@ -0,0 +1,338 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { FormGroup, FormArray } from '@angular/forms'; +import { EmploymentInfoPageService } from './employment-info-page.service'; +import { ActivatedRoute } from '@angular/router'; +import { Subject, Subscription } from 'rxjs'; +import { BorrowerType } from 'src/app/shared/borrower-type'; + +@Component({ + selector: 'lw-employment-info-page', + templateUrl: './employment-info-page.component.html' +}) +export class EmploymentInfoPageComponent implements OnInit, OnDestroy { + private valueSubscription: Subscription; + private statusSubscription: Subscription; + public savingData = null; + + public localState = { + previousDisabled: false, + nextDisabled: false, + borrowerType: BorrowerType.PRIMARY_BORROWER, + borrowerName: 'Applicant', + nextTopic: '', + isSigningSpouse: false, + MAX_INCOME_EMPLOY: 2, + MAX_INCOME_OTHER: 2 + }; + + public pageForm: FormGroup; + public financialInfoGroup: FormGroup; + public employmentIncomeInfoArray: FormArray; + public otherIncomeInfoArray: FormArray; + public alimonyInfoGroup: FormGroup; + public isCCApplication: boolean; + public loanId: string; + public borrowerType: string; + public showProofOfIncomeUploadForm: boolean; + + public removeEmploymentIncomeFiles: Array>; + public removeOtherIncomeFiles: Array>; + + private readonly FORM_STATE_INVALID = 'INVALID'; + + constructor( + private employmentInfoPageService: EmploymentInfoPageService, + public route: ActivatedRoute + ) { + this.removeEmploymentIncomeFiles = [new Subject(),new Subject(),new Subject()]; + this.removeOtherIncomeFiles = [new Subject(),new Subject(),new Subject()]; + } + + public ngOnInit() { + this.initializeFormGroups(); + this.updateLocalState(this.pageForm.value); + this.loanId = this.employmentInfoPageService.getLoanId(); + this.borrowerType = this.formatBorrowerType(this.employmentInfoPageService.determineBorrowerType(this.route)); + this.showProofOfIncomeUploadForm = this.employmentInfoPageService.showProofOfIncomeUploadForm(); + this.isCCApplication = this.employmentInfoPageService.isCCApplication(); + } + + public initializeFormGroups() { + this.pageForm = this.employmentInfoPageService.createPageGroup(); + this.statusSubscription = this.pageForm.statusChanges.subscribe( + newStatus => { + this.localState.nextDisabled = newStatus === this.FORM_STATE_INVALID; + } + ); + this.employmentIncomeInfoArray = this.pageForm.get(this.employmentInfoPageService.EMPLOYMENT_FORM_NAME) as FormArray; + this.otherIncomeInfoArray = this.pageForm.get(this.employmentInfoPageService.OTHER_INCOME_FORM_NAME) as FormArray; + this.financialInfoGroup = this.pageForm.get(this.employmentInfoPageService.FINANCIAL_FORM_NAME) as FormGroup; + const borrowerType = this.employmentInfoPageService.determineBorrowerType(this.route); + this.financialInfoGroup.patchValue(this.employmentInfoPageService.getFinancialSelection(borrowerType)); + } + + public updateLocalState(nextValues) { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + + // Because it is used in the assignment. + const borrowerType = this.employmentInfoPageService.determineBorrowerType(this.route); + this.localState = { + previousDisabled: false, + nextDisabled: this.localState.nextDisabled, + borrowerType: borrowerType, + borrowerName: this.employmentInfoPageService.getApplicantName(borrowerType), + nextTopic: this.employmentInfoPageService.getNextTopicMessage(), + isSigningSpouse: this.employmentInfoPageService.individualIsSigningSpouse(borrowerType), + MAX_INCOME_EMPLOY: this.localState.MAX_INCOME_EMPLOY, + MAX_INCOME_OTHER: this.localState.MAX_INCOME_OTHER + }; + + this.updateEmploymentIncomeGroups(this.financialInfoGroup.value); + this.updateConditionallyShownEmploymentFields(); + this.updateOtherIncomeGroups(this.financialInfoGroup.value); + this.updateAlimonyGroup(this.financialInfoGroup.value.isPayingChildSupport); + this.valueSubscription = this.pageForm.valueChanges.subscribe(async value => { + this.updateLocalState(value); + }); + } + + public updateConditionallyShownEmploymentFields(): void { + for (let index = 0; index < this.employmentIncomeInfoArray.length; index++) { + const group = this.employmentIncomeInfoArray.at(index) as FormGroup; + const value = group.value; + if (value.schedule === 'Hourly' && value.employmentType !== 'FullTime') { + this.employmentInfoPageService.addWeeklyHoursIfNeeded( + group, + index, + this.localState.borrowerType + ); + } else { + this.employmentInfoPageService.removeWeeklyHoursIfPresent(group); + } + } + } + + public updateAlimonyGroup(isPayingChildSupport: boolean): void { + if (!this.alimonyInfoGroup && isPayingChildSupport) { + this.pageForm.addControl( + this.employmentInfoPageService.ALIMONY_FORM_NAME, + this.employmentInfoPageService.createAlimonyInformationGroup() + ); + this.alimonyInfoGroup = this.pageForm.get(this.employmentInfoPageService.ALIMONY_FORM_NAME) as FormGroup; + this.alimonyInfoGroup.patchValue(this.employmentInfoPageService.getAlimonyValue(this.localState.borrowerType)); + } else if (this.alimonyInfoGroup && !isPayingChildSupport) { + this.pageForm.removeControl( + this.employmentInfoPageService.ALIMONY_FORM_NAME + ); + this.alimonyInfoGroup = undefined; + } + } + + // This is "any" because of the dubious "hasEmploymentIncome0", etc. + public updateOtherIncomeGroups(otherValue: any) { + let showingCount = this.otherIncomeInfoArray.length; + const expectedCount = this.determineExpectedOtherIncomeCount(otherValue); + if (showingCount > expectedCount) { + this.removeAdditionalOtherIncome(this.determineOtherIncomeRemoveIndex(otherValue)); + // may have unchecked a non-last income, so shift the check boxes. + this.shiftFinancialOtherIncome(otherValue); + showingCount = this.otherIncomeInfoArray.length; + // We patch this back because we shifted the booleans. + this.financialInfoGroup.patchValue(otherValue); + } + while (showingCount < expectedCount) { + this.addAdditionalOtherIncome(); + showingCount = this.otherIncomeInfoArray.length; + } + } + + // This is "any" because of the dubious "hasEmploymentIncome0", etc. + public updateEmploymentIncomeGroups(otherValue: any) { + let showingCount = this.employmentIncomeInfoArray.length; + const expectedCount = this.determineExpectedEmploymentIncomeCount(otherValue); + if (showingCount > expectedCount) { + this.removeAdditionalEmploymentIncome(this.determineEmploymentIncomeRemoveIndex(otherValue)); + // may have unchecked a non-last income, so shift the check boxes. + this.shiftFinancialEmploymentIncome(otherValue); + showingCount = this.employmentIncomeInfoArray.length; + // We patch this back because we shifted the booleans. + this.financialInfoGroup.patchValue(otherValue); + } + while (showingCount < expectedCount) { + this.addAdditionalEmploymentIncome(); + showingCount = this.employmentIncomeInfoArray.length; + } + } + + public removeAdditionalOtherIncome(index: number) { + // Check if the upload form is shown and page contains the form + // if there is no income form, remove the form + // This is because the upload component emits when all files are removed + if(this.showProofOfIncomeUploadForm && this.pageForm.contains(this.employmentInfoPageService.OTHER_INCOME_FORM_NAME)) { + this.removeOtherIncomeFiles[index].next(); + } else if(!this.showProofOfIncomeUploadForm && this.pageForm.contains(this.employmentInfoPageService.OTHER_INCOME_FORM_NAME)) { + this.removedOtherIncomeFiles(true, index); + } + } + + public removedOtherIncomeFiles(removedAllFiles, index) { + if(removedAllFiles) { + this.pageForm.removeControl(this.employmentInfoPageService.OTHER_INCOME_FORM_NAME); + this.otherIncomeInfoArray.removeAt(index); + this.pageForm.addControl(this.employmentInfoPageService.OTHER_INCOME_FORM_NAME, this.otherIncomeInfoArray); + } + } + + public removeAdditionalEmploymentIncome(index: number) { + // Check if the upload form is shown and page contains the form + // if there is no income form, remove the form + // This is because the upload component emits when all files are removed + if(this.showProofOfIncomeUploadForm && this.pageForm.contains(this.employmentInfoPageService.EMPLOYMENT_FORM_NAME)) { + this.removeEmploymentIncomeFiles[index].next(); + } else if(!this.showProofOfIncomeUploadForm && this.pageForm.contains(this.employmentInfoPageService.EMPLOYMENT_FORM_NAME)) { + this.removedEmploymentIncomeFiles(true, index); + } + } + + public removedEmploymentIncomeFiles(removedAllFiles, index) { + if(removedAllFiles) { + this.pageForm.removeControl(this.employmentInfoPageService.EMPLOYMENT_FORM_NAME); + this.employmentIncomeInfoArray.removeAt(index); + this.pageForm.addControl(this.employmentInfoPageService.EMPLOYMENT_FORM_NAME, this.employmentIncomeInfoArray); + } + } + + public addAdditionalOtherIncome() { + const data = this.employmentInfoPageService.getOtherIncomeValue(this.localState.borrowerType); + const group = this.employmentInfoPageService.createOtherIncomeInformationGroup(); + this.pageForm.removeControl(this.employmentInfoPageService.OTHER_INCOME_FORM_NAME); + this.otherIncomeInfoArray.push(group); + if (data && data.length >= this.otherIncomeInfoArray.length) { + this.otherIncomeInfoArray.at(this.otherIncomeInfoArray.length - 1).patchValue(data[this.otherIncomeInfoArray.length - 1]); + } + this.pageForm.addControl(this.employmentInfoPageService.OTHER_INCOME_FORM_NAME, this.otherIncomeInfoArray); + } + + public addAdditionalEmploymentIncome() { + const data = this.employmentInfoPageService.getEmploymentIncomeValue(this.localState.borrowerType); + const group = this.employmentInfoPageService.createEmploymentInformationGroup(); + this.pageForm.removeControl(this.employmentInfoPageService.EMPLOYMENT_FORM_NAME); + this.employmentIncomeInfoArray.push(group); + if (data && data.length >= this.employmentIncomeInfoArray.length) { + this.employmentIncomeInfoArray.at(this.employmentIncomeInfoArray.length - 1) + .patchValue(data[this.employmentIncomeInfoArray.length - 1]); + } + this.pageForm.addControl(this.employmentInfoPageService.EMPLOYMENT_FORM_NAME, this.employmentIncomeInfoArray); + } + + public shiftFinancialOtherIncome(otherValues: any): void { + if (otherValues) { + // Has to be less than so we don't go off the end with the +1 + for (let index = 0; index < this.localState.MAX_INCOME_OTHER; index++) { + if (!otherValues['hasOtherIncome' + index] && otherValues['hasOtherIncome' + (index + 1)]) { + otherValues['hasOtherIncome' + index] = true; + otherValues['hasOtherIncome' + (index + 1)] = false; + } + } + } + } + + public shiftFinancialEmploymentIncome(employmentValues: any): void { + if (employmentValues) { + // Has to be less than so we don't go off the end with the +1 + for (let index = 0; index < this.localState.MAX_INCOME_EMPLOY; index++) { + if (!employmentValues['hasEmploymentIncome' + index] && employmentValues['hasEmploymentIncome' + (index + 1)]) { + employmentValues['hasEmploymentIncome' + index] = true; + employmentValues['hasEmploymentIncome' + (index + 1)] = false; + } + } + } + } + + public determineExpectedOtherIncomeCount(otherValues: any): number { + let count = 0; + if (!otherValues) { + return count; + } + for (let index = 0; index <= this.localState.MAX_INCOME_OTHER; index++) { + if (otherValues['hasOtherIncome' + index]) { + count++; + } + } + return count; + } + + public determineExpectedEmploymentIncomeCount(employmentValues: any): number { + let count = 0; + if (!employmentValues) { + return count; + } + for (let index = 0; index <= this.localState.MAX_INCOME_EMPLOY; index++) { + if (employmentValues['hasEmploymentIncome' + index]) { + count++; + } + } + return count; + } + + public determineOtherIncomeRemoveIndex(otherValues: any): number { + if (otherValues) { + for (let index = 0; index <= this.localState.MAX_INCOME_OTHER; index++) { + if (!otherValues['hasOtherIncome' + index]) { + return index; + } + } + } + throw new Error('Other values expected'); + } + + public determineEmploymentIncomeRemoveIndex(employmentValues: any): number { + if (employmentValues) { + for (let index = 0; index <= this.localState.MAX_INCOME_EMPLOY; index++) { + if (!employmentValues['hasEmploymentIncome' + index]) { + return index; + } + } + } + throw new Error('Other values expected'); + } + + public ngOnDestroy() { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + + const alimonyGroup = this.alimonyInfoGroup ? this.alimonyInfoGroup.value : null; + const employmentIncomes = this.employmentIncomeInfoArray.value; + const otherIncomes = this.otherIncomeInfoArray.value; + this.employmentInfoPageService + .saveAndNavigate(alimonyGroup, employmentIncomes, otherIncomes, this.route, this.localState.borrowerType); + this.savingData = { + isActive: true + }; + this.localState.nextDisabled = true; + } + + public commandPrevious() { + this.employmentInfoPageService.cancelAndNavigate(this.route); + } + + // Returns a formatted string so that ACTion has additional details for file type + public formatBorrowerType(borrowerType: BorrowerType) { + let formattedBorrowerType = borrowerType.toString(); + formattedBorrowerType = formattedBorrowerType.split("_").join(" ").toLowerCase(); + formattedBorrowerType = formattedBorrowerType.charAt(0).toUpperCase() + formattedBorrowerType.slice(1) + return formattedBorrowerType; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.service.spec.ts new file mode 100644 index 0000000..9ecdebe --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.service.spec.ts @@ -0,0 +1,127 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ReactiveFormsModule, FormGroup } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { EmploymentInfoPageService } from './employment-info-page.service'; +import { EmploymentInformation } from '../../../field-groups/employment/employment-information'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { BorrowerType } from '../../../../shared/borrower-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { MockConfigurationService } from 'src/app/testing/shared-mocks/configuration.mock.service'; +import { DefaultSettings } from 'src/app/shared/configuration/default-settings.mock'; +import { UiSettings } from 'src/app/shared/configuration/ui-settings'; +import { IncomeInformation } from 'src/app/loan-wizard/field-groups/employment/income-information'; +import { AlimonyInformation } from '../../../field-groups/employment/alimony-information'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + + +describe('Employment Info Page Service', () => { + + let service: EmploymentInfoPageService; + let applicationWizardService: ApplicationWizardService; + let configService: ConfigurationService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let creditApplicationService: CreditApplicationService; + let router: Router; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: ConfigurationService, useValue: MockConfigurationService}, + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(EmploymentInfoPageService); + configService = TestBed.get(MockConfigurationService); + configService.saveConfiguration(defaultSettings); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + creditApplicationService = TestBed.get(CreditApplicationService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create the top level form group', () => { + spyOn(service, 'createFinancialInformationGroup').and.callThrough(); + service.createPageGroup(); + expect(service.createFinancialInformationGroup).toHaveBeenCalled(); + }); + + it('should create an employment info group', () => { + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + const employmentGroup = service.createEmploymentInformationGroup(); + expect(employmentGroup).toBeTruthy(); + CommonGroups.EMPLOYMENT_INFO.fields.forEach((eachField) => { + expect(employmentGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should provide the value for the groups', () => { + const testGroupName = 'testGroup'; + spyOn(applicationWizardService, 'getIndividualGroupValues').and.returnValue(true); + service.getApplicationGroupValues(testGroupName, BorrowerType.PRIMARY_BORROWER); + expect(applicationWizardService.getIndividualGroupValues).toHaveBeenCalledWith(testGroupName, BorrowerType.PRIMARY_BORROWER); + }); + + it('should navigate the application, on successful save', () => { + spyOn(creditApplicationService, 'saveAllEmploymentInformation').and.callThrough().and.returnValue(of(true)); + spyOn(router, 'navigate'); + spyOn(applicationWizardService, 'getNextRoute').and.returnValue('foo bar'); + const info = {}; + const employmentInfo = Array(); + const otherIncomeInfo = Array(); + const activatedRoute = {}; + service.saveAndNavigate(info, employmentInfo, otherIncomeInfo, activatedRoute, BorrowerType.PRIMARY_BORROWER); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'saveAllEmploymentInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + spyOn(applicationWizardService, 'getNextRoute').and.returnValue('foo bar'); + const alimonyInfo = {}; + const employmentInfo = Array(); + const otherIncomeInfo = Array(); + const activatedRoute = {}; + service.saveAndNavigate(alimonyInfo, employmentInfo, otherIncomeInfo, activatedRoute, BorrowerType.PRIMARY_BORROWER); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('cancelling should always navigate', () => { + spyOn(router, 'navigate'); + spyOn(service, 'getRouteName').and.returnValue('step-4'); + const activatedRoute = {}; + service.cancelAndNavigate(activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should default the number of months to "0"', () => { + spyOn(service, 'getIncomeLimits').and.returnValue(defaultSettings.incomeLimits); + const employmentGroup: FormGroup = service.createEmploymentInformationGroup(); + expect(employmentGroup.value.employmentLengthMonths).toBe('0'); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.service.ts new file mode 100644 index 0000000..f98b2c7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/employment-info-page.service.ts @@ -0,0 +1,248 @@ +import { Injectable } from '@angular/core'; +import { FormGroup, FormBuilder, FormArray } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { EmploymentInformation } from '../../../field-groups/employment/employment-information'; +import { PageService } from '../../page-service'; +import { BorrowerType } from '../../../../shared/borrower-type'; +import { AlimonyInformation } from '../../../field-groups/employment/alimony-information'; +import { IncomeInformation } from '../../../field-groups/employment/income-information'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { IncomeLimits } from 'src/app/shared/configuration/ui-settings'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; +import { FormatService } from 'src/app/shared/pipes/format.service'; +import { LoanType } from 'src/app/shared/loan-type'; +import { LoanCategory } from 'src/app/shared/loan-type/loan-category'; + +@Injectable({ + providedIn: 'root' +}) +export class EmploymentInfoPageService implements PageService { + + public readonly EMPLOYMENT_FORM_NAME = 'employmentInformation'; + public readonly FINANCIAL_FORM_NAME = 'financialInformation'; + public readonly OTHER_INCOME_FORM_NAME = 'otherIncomeInformation'; + public readonly ALIMONY_FORM_NAME = 'alimonyInformation'; + public readonly INCOME_LIMIT_CONFIG_FIELD = 'incomeLimits'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private configurationService: ConfigurationService, + private formatService: FormatService, + private router: Router, + private formBuilder: FormBuilder) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + const financialGroup = this.createFinancialInformationGroup(); + const otherIncomeArray = this.createEmptyFormArray(); + const employmentIncomeArray = this.createEmptyFormArray(); + group.addControl(this.FINANCIAL_FORM_NAME, financialGroup); + group.addControl(this.OTHER_INCOME_FORM_NAME, otherIncomeArray); + group.addControl(this.EMPLOYMENT_FORM_NAME, employmentIncomeArray); + return group; + } + + public createEmptyFormArray(): FormArray { + return this.formBuilder.array([]); + } + + public createFinancialInformationGroup(): FormGroup { + const financialGroup = this.formBuilder.group({}); + + CommonGroups.FINANCIAL_INFORMATION.fields.forEach((eachField) => { + financialGroup.addControl(eachField.field, this.formBuilder.control('')); + financialGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + return financialGroup; + } + + public createEmploymentInformationGroup(): FormGroup { + const employmentGroup = this.formBuilder.group({}); + + CommonGroups.EMPLOYMENT_INFO.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + validators.push(validationFunction(this.getIncomeLimits())); + }, this); + } + employmentGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + + employmentGroup.patchValue(this.generateEmploymentInformationDefaults()); + return employmentGroup; + } + + public generateEmploymentInformationDefaults() { + return { + employmentLengthMonths: '0' + }; + } + + public createEmploymentIncomeInformationGroup(): FormGroup { + const employmentIncomeGroup = this.formBuilder.group({}); + CommonGroups.EMPLOYMENT_INFO.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + validators.push(validationFunction(this.getIncomeLimits())); + }, this); + } + employmentIncomeGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + + employmentIncomeGroup.patchValue(this.generateEmploymentInformationDefaults()); + return employmentIncomeGroup; + } + + public createOtherIncomeInformationGroup(): FormGroup { + const otherIncomeGroup = this.formBuilder.group({}); + + CommonGroups.INCOME_INFO.fields.forEach((eachField) => { + otherIncomeGroup.addControl(eachField.field, this.formBuilder.control('')); + otherIncomeGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + return otherIncomeGroup; + } + + public createAlimonyInformationGroup(): FormGroup { + const alimonyGroup = this.formBuilder.group({}); + + CommonGroups.ALIMONY_INFO.fields.forEach((eachField) => { + alimonyGroup.addControl(eachField.field, this.formBuilder.control('')); + alimonyGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + return alimonyGroup; + } + + public getFinancialSelection(borrowerType: BorrowerType): any { + const isPayingChildSupport = this.getAlimonyValue(borrowerType); + const hasOtherIncomes: Array = this.getOtherIncomeValue(borrowerType); + const hasEmploymentIncomes: Array = this.getEmploymentIncomeValue(borrowerType); + return { + isPayingChildSupport: isPayingChildSupport && isPayingChildSupport.childSupportAmount, + hasOtherIncome0: hasOtherIncomes.length > 0, + hasOtherIncome1: hasOtherIncomes.length > 1, + hasOtherIncome2: hasOtherIncomes.length > 2, + hasEmploymentIncome0: hasEmploymentIncomes.length > 0, + hasEmploymentIncome1: hasEmploymentIncomes.length > 1, + hasEmploymentIncome2: hasEmploymentIncomes.length > 2, + }; + } + + public getAlimonyValue(borrowerType: BorrowerType): AlimonyInformation { + return this.applicationWizardService.getIndividualGroupValues(this.ALIMONY_FORM_NAME, borrowerType); + } + + public getOtherIncomeValue(borrowerType: BorrowerType): Array { + const otherIncomeData = >this.applicationWizardService + .getIndividualGroupValues(this.OTHER_INCOME_FORM_NAME, borrowerType); + + if (otherIncomeData.length) { + return otherIncomeData; + } + return []; + } + + public getEmploymentIncomeValue(borrowerType: BorrowerType): Array { + const employmentIncomeData = >this.applicationWizardService + .getIndividualGroupValues(this.EMPLOYMENT_FORM_NAME, borrowerType); + + if (employmentIncomeData.length) { + return employmentIncomeData; + } + return []; + } + + public getApplicationGroupValues(groupName: string, individual: BorrowerType): any { + return this.applicationWizardService.getIndividualGroupValues(groupName, individual); + } + + public saveAndNavigate(alimonyInfo: AlimonyInformation, employmentIncomeInfo: Array, + otherIncomeInfo: Array, route: ActivatedRoute, individual: BorrowerType) { + this.creditApplicationService.saveAllEmploymentInformation(employmentIncomeInfo, otherIncomeInfo, alimonyInfo, individual) + .subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.getRouteName()); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.getRouteName()); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public individualIsSigningSpouse(borrowerType: BorrowerType): boolean { + return borrowerType === BorrowerType.PRIMARY_BORROWER_SPOUSE || borrowerType === BorrowerType.CO_BORROWER_SPOUSE; + } + + public cancelAndNavigate(route: ActivatedRoute) { + const nextRoute = this.applicationWizardService.getPreviousRoute(this.getRouteName()); + this.router.navigate([nextRoute], {relativeTo: route}); + } + + public getIncomeLimits(): IncomeLimits { + return this.configurationService.getConfigurationByField(this.INCOME_LIMIT_CONFIG_FIELD); + } + + public getRouteName(): string { + return this.applicationWizardService.getSimpleRouteName(this.router.url); + } + + public getNextTopicMessage() { + return this.applicationWizardService.getNextTopicMessage(this.getRouteName()); + } + + public getApplicantName(borrowerType: BorrowerType): string { + return this.formatService.formatTitleCase( + this.applicationWizardService.getApplicantName(borrowerType) + ); + } + + public determineBorrowerType(route: ActivatedRoute): BorrowerType { + if (route.snapshot.data[0]) { + return route.snapshot.data[0].individual; + } + return BorrowerType.PRIMARY_BORROWER; + } + + public addWeeklyHoursIfNeeded(group: FormGroup, index: number, borrowerType: BorrowerType): void { + if (group.get('weeklyHours')) { return; } + const existingValue = this.getEmploymentIncomeValue(borrowerType)[index] || {}; + CommonGroups.WEEKLY_HOURS_INFO.fields.forEach((eachField) => { + group.addControl(eachField.field, this.formBuilder.control(existingValue[eachField.field], eachField.validation)); + }, this); + } + + public removeWeeklyHoursIfPresent(group: FormGroup): void { + if (!group.get('weeklyHours')) { return; } + CommonGroups.WEEKLY_HOURS_INFO.fields.forEach((eachField) => { + group.removeControl(eachField.field); + }, this); + } + + public getLoanId() : string { + return this.applicationWizardService.getFieldValue("loanId"); + } + + public showProofOfIncomeUploadForm(): boolean { + return this.applicationWizardService.showProofOfIncomeUploadForm(); + } + + public isCCApplication(): boolean { + let loanType = this.applicationWizardService.getFieldValue('loanType') as LoanType; + return loanType.category === LoanCategory.CreditCard; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-employment-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-employment-info-page.spec.ts new file mode 100644 index 0000000..e25ccfd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-employment-info-page.spec.ts @@ -0,0 +1,60 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoBorrowerEmploymentInfoPage } from './co-borrower-employment-info-page'; +import { configureTestSuite } from 'ng-bullet'; + +describe('CoBorrower Employment Info Page', () => { + + let page: CoBorrowerEmploymentInfoPage; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new CoBorrowerEmploymentInfoPage('PAGETITLES.COMMON.COAPP_EMPLOYMENT_INFO', + 'co-applicant-financial', + [ + CommonGroups.FINANCIAL_INFORMATION + ]); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isCoApplicant').and.callThrough().and.returnValue(true); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isCoApplicant).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isCoApplicant').and.callThrough().and.returnValue(false); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isCoApplicant).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-employment-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-employment-info-page.ts new file mode 100644 index 0000000..3f2b21d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-employment-info-page.ts @@ -0,0 +1,15 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { PrimaryEmploymentInfoPage } from './primary-employment-info-page'; + +export class CoBorrowerEmploymentInfoPage extends PrimaryEmploymentInfoPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + return applicationWizardService.isCoApplicant(); + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.CO_BORROWER; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-spouse-employment-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-spouse-employment-info-page.spec.ts new file mode 100644 index 0000000..d11bb97 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-spouse-employment-info-page.spec.ts @@ -0,0 +1,68 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoBorrowerSpouseEmploymentInfoPage } from './co-borrower-spouse-employment-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; +import { configureTestSuite } from 'ng-bullet'; + +describe('CoBorrower Spouse Employment Info Page', () => { + + let page: CoBorrowerSpouseEmploymentInfoPage; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new CoBorrowerSpouseEmploymentInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_EMPLOYMENT_INFO', + 'co-applicant-spouse-financial', + [ + CommonGroups.FINANCIAL_INFORMATION + ] + ); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.FullInfoSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.TattleSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-spouse-employment-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-spouse-employment-info-page.ts new file mode 100644 index 0000000..26685f4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/co-borrower-spouse-employment-info-page.ts @@ -0,0 +1,18 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { BorrowerType } from '../../../../../shared/borrower-type'; +import { PrimaryEmploymentInfoPage } from './primary-employment-info-page'; +import { SpouseType } from 'src/app/shared/credit-application'; + +export class CoBorrowerSpouseEmploymentInfoPage extends PrimaryEmploymentInfoPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + if (applicationWizardService.isApplicantNonSpouse(this.getBorrowerType())) { + return applicationWizardService.getSpouseType(this.getBorrowerType()) === SpouseType.FullInfoSpouse; + } + return false; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.CO_BORROWER; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-employment-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-employment-info-page.spec.ts new file mode 100644 index 0000000..22975e9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-employment-info-page.spec.ts @@ -0,0 +1,62 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed, async } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { PrimaryEmploymentInfoPage } from './primary-employment-info-page'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { BorrowerType } from '../../../../../shared/borrower-type'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + +describe('Primary Employment Info Page', () => { + + let page: PrimaryEmploymentInfoPage; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new PrimaryEmploymentInfoPage('PAGETITLES.COMMON.EMPLOYMENT_INFO', 'step-4', + [ + CommonGroups.EMPLOYMENT_INFO + ]); + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should always return primary borrower type', () => { + spyOn(page, 'getBorrowerType').and.callThrough(); + const borrowerType = page.getBorrowerType(); + expect(page.getBorrowerType).toHaveBeenCalled(); + expect(borrowerType).toBe(BorrowerType.PRIMARY_BORROWER); + }); + + it('should correctly determine if page is valid ( default valid )', () => { + spyOn(page, 'isValidPage').and.callThrough(); + const errors = page.isValidPage(applicationWizardService, creditApplicationService); + expect(page.isValidPage).toHaveBeenCalled(); + expect(errors).toEqual([]); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-employment-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-employment-info-page.ts new file mode 100644 index 0000000..f592a9a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-employment-info-page.ts @@ -0,0 +1,54 @@ +import { ApplicationPage } from '../../../../application-page'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; +import {LoanTitle} from '../../../../../shared/loan-type/loan-title'; + +export class PrimaryEmploymentInfoPage extends ApplicationPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + return applicationWizardService.getGroupValues('loanType').title !== LoanTitle.HELOC_RateMod; + } + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + let value; + let currentGroup; + const errors = []; + const borrowerType = this.getBorrowerType(); + + const financialGroups = { + employmentInformation: applicationWizardService + .getIndividualGroupValues('employmentInformation', borrowerType), + otherIncomeInformation: applicationWizardService + .getIndividualGroupValues('otherIncomeInformation', borrowerType), + alimonyInformation: applicationWizardService + .getIndividualGroupValues('alimonyInformation', borrowerType) + }; + this.fieldgroup.forEach((pageFieldGroup) => { + currentGroup = financialGroups[pageFieldGroup.group]; + if (currentGroup && Object.keys(currentGroup).length) { + if (currentGroup.length) { + currentGroup.forEach((group) => { + pageFieldGroup.fields.forEach((pageField) => { + value = group[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }); + }); + } else { + pageFieldGroup.fields.forEach((pageField) => { + value = currentGroup[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }, this); + } + } + }, this); + return errors; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.PRIMARY_BORROWER; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-spouse-employment-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-spouse-employment-info-page.spec.ts new file mode 100644 index 0000000..2412a45 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-spouse-employment-info-page.spec.ts @@ -0,0 +1,68 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { PrimarySpouseEmploymentInfoPage } from './primary-spouse-employment-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Primary Spouse Employment Info Page', () => { + + let page: PrimarySpouseEmploymentInfoPage; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new PrimarySpouseEmploymentInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_EMPLOYMENT_INFO', + 'applicant-spouse-financial', + [ + CommonGroups.FINANCIAL_INFORMATION + ] + ); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.FullInfoSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.TattleSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-spouse-employment-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-spouse-employment-info-page.ts new file mode 100644 index 0000000..60fdf73 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/employment/pages/primary-spouse-employment-info-page.ts @@ -0,0 +1,19 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { BorrowerType } from '../../../../../shared/borrower-type'; +import { PrimaryEmploymentInfoPage } from './primary-employment-info-page'; +import { SpouseType } from 'src/app/shared/credit-application'; + +export class PrimarySpouseEmploymentInfoPage extends PrimaryEmploymentInfoPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + if (applicationWizardService.isApplicantNonSpouse(this.getBorrowerType())) { + return applicationWizardService.getSpouseType(this.getBorrowerType()) === SpouseType.FullInfoSpouse; + } + return false; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.PRIMARY_BORROWER; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.html new file mode 100644 index 0000000..e313c77 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.html @@ -0,0 +1,37 @@ +
+
+
+

HEADINGS.HMDA.HMDA

+
+
COPY.HMDA
+
+

HMDA.HMDA_INTRO

+
+
+ +
+
+
+
+

HEADINGS.HMDA.ETHNICITY.{{localState.borrowerType}}

+ +
+
+

HEADINGS.HMDA.RACE.{{localState.borrowerType}}

+ +
+
+

HEADINGS.HMDA.SEX.{{localState.borrowerType}}

+ +
+
+
+
+
+
+ + +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.spec.ts new file mode 100644 index 0000000..6b893a9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.spec.ts @@ -0,0 +1,141 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { HmdaInfoPageComponent } from './hmda-info-page.component'; +import { HmdaInfoPageService } from './hmda-info-page.service'; +import { EthnicityInfoFormComponent } from '../../../field-groups/hmda/ethnicity-info-form.component'; +import { GenderInfoFormComponent} from '../../../field-groups/hmda/gender-info-form.component'; +import { RaceInfoFormComponent } from '../../../field-groups/hmda/race-info-form.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { apiTranslatorPreTranslate } from '../../../../testing/shared-mocks/api-translator-pre-post'; +import { ErrorDisplayComponent } from '../../../controls/error-display/error-display.component'; +import { configureTestSuite } from 'ng-bullet'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +describe('HmdaInfoPageComponent', () => { + let component: HmdaInfoPageComponent; + let fixture: ComponentFixture; + let service: HmdaInfoPageService; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + HmdaInfoPageComponent, + EthnicityInfoFormComponent, + GenderInfoFormComponent, + RaceInfoFormComponent, + ErrorDisplayComponent + ], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[5].id); + fixture = TestBed.createComponent(HmdaInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(HmdaInfoPageService); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.pageForm).toBeTruthy(); + expect(component.ethnicityInfoGroup).toBeTruthy(); + expect(component.raceInfoGroup).toBeTruthy(); + expect(component.genderInfoGroup).toBeTruthy(); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + expect(component.commandPrevious).not.toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + fixture.detectChanges(); + spyOn(service, 'saveAndNavigate'); + component.localState.nextDisabled = false; + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + spyOn(service, 'cancelAndNavigate'); + component.commandPrevious(); + expect(service.cancelAndNavigate).toHaveBeenCalled(); + }); + + it('should bind the previous button to the correct command function', () => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonPrevious')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).not.toHaveBeenCalled(); + expect(component.commandPrevious).toHaveBeenCalled(); + }); + }); + + it('should render existing HMDA data', () => { + const raceInfo = apiTranslatorPreTranslate.fullInfoBorrowers[0].raceInformation; + const genderInfo = apiTranslatorPreTranslate.fullInfoBorrowers[0].genderInformation; + const ethnicityInfo = apiTranslatorPreTranslate.fullInfoBorrowers[0].ethnicityInformation; + spyOn(service, 'getRaceFormValues').and.returnValue(raceInfo); + spyOn(service, 'getGenderFormValues').and.returnValue(genderInfo); + spyOn(service, 'getEthnicityFormValues').and.returnValue(ethnicityInfo); + fixture.detectChanges(); + component.raceInfoGroup.updateValueAndValidity(); + component.genderInfoGroup.updateValueAndValidity(); + component.ethnicityInfoGroup.updateValueAndValidity(); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.raceInfoGroup.valid).toBeTruthy(); + expect(component.genderInfoGroup.valid).toBeTruthy(); + expect(component.ethnicityInfoGroup.valid).toBeTruthy(); + fixture.detectChanges(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.raceInfoGroup.value).toEqual(raceInfo); + expect(component.genderInfoGroup.value).toEqual(genderInfo); + expect(component.ethnicityInfoGroup.value).toEqual(ethnicityInfo); + }); + + it('should have an initially invalid page', () => { + fixture.detectChanges(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeTruthy(); + expect(component.pageForm.valid).toBeFalsy(); + }); + + it('should make page invalid if both male and female are selected', () => { + fixture.detectChanges(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + component.genderInfoGroup.patchValue({male: true, female: true}); + fixture.detectChanges(); + expect(buttonNext.disabled).toBeTruthy(); + expect(component.pageForm.valid).toBeFalsy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.ts new file mode 100644 index 0000000..68978a5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.component.ts @@ -0,0 +1,82 @@ +import { Component, OnDestroy, OnInit, AfterContentChecked, AfterContentInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { HmdaInfoPageService } from './hmda-info-page.service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'lw-hmda-info-page', + templateUrl: './hmda-info-page.component.html' +}) +export class HmdaInfoPageComponent implements OnInit, OnDestroy, AfterContentInit { + public raceInfoGroup: FormGroup; + public ethnicityInfoGroup: FormGroup; + public genderInfoGroup: FormGroup; + private readonly FORM_STATE_INVALID = 'INVALID'; + private statusSubscription: Subscription; + + public pageForm: FormGroup; + public readonly applicantGroupName = 'applicant'; + + public savingData = null; + + public localState = { + previousDisabled: false, + nextDisabled: true, + nextTopic: '', + borrowerType: BorrowerType.PRIMARY_BORROWER, + borrowerName: '', + }; + + constructor( + private hmdaInfoPageService: HmdaInfoPageService, + private route: ActivatedRoute) { } + + public ngOnInit() { + this.localState.borrowerType = this.hmdaInfoPageService.determineBorrowerType(this.route); + this.localState.borrowerName = this.hmdaInfoPageService.getApplicantName(this.localState.borrowerType); + this.pageForm = this.hmdaInfoPageService.createPageGroup(); + this.raceInfoGroup = this.pageForm.get(HmdaInfoPageService.RACE_FORM_NAME); + this.genderInfoGroup = this.pageForm.get(HmdaInfoPageService.GENDER_FORM_NAME); + this.ethnicityInfoGroup = this.pageForm.get(HmdaInfoPageService.ETHNICITY_FORM_NAME); + this.patchInfoGroupValues(); + this.localState.nextDisabled = !this.pageForm.valid; + this.localState.nextTopic = this.hmdaInfoPageService.getNextTopicMessage(); + } + + public ngAfterContentInit() { + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + } + + public ngOnDestroy() { + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public patchInfoGroupValues() { + this.raceInfoGroup.patchValue( + this.hmdaInfoPageService.getRaceFormValues(this.localState.borrowerType)); + this.genderInfoGroup.patchValue( + this.hmdaInfoPageService.getGenderFormValues(this.localState.borrowerType)); + this.ethnicityInfoGroup.patchValue( + this.hmdaInfoPageService.getEthnicityFormValues(this.localState.borrowerType)); + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + + this.hmdaInfoPageService.saveAndNavigate(this.pageForm.value, this.route, this.localState.borrowerType); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + } + + public commandPrevious() { + this.hmdaInfoPageService.cancelAndNavigate(this.route); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.service.spec.ts new file mode 100644 index 0000000..acc53b9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.service.spec.ts @@ -0,0 +1,111 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { HmdaInfoPageService } from './hmda-info-page.service'; +import { EthnicityInformation } from '../../../field-groups/hmda/ethnicity-information'; +import { RaceInformation } from '../../../field-groups/hmda/race-information'; +import { GenderInformation } from '../../../field-groups/hmda/gender-information'; +import { BorrowerType } from '../../../../shared/borrower-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + + +describe('HMDA Info Page Service', () => { + + let service: HmdaInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let creditApplicationService: CreditApplicationService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(HmdaInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[4].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + creditApplicationService = TestBed.get(CreditApplicationService); + router = TestBed.get(Router); + spyOn(service, 'getNextTopicMessage').and.callThrough().and.returnValue('foo bar'); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create the top level page group', () => { + spyOn(service, 'createPageGroup').and.callThrough(); + service.createPageGroup(); + expect(service.createPageGroup).toHaveBeenCalled(); + }); + + it('should create an ethnicity info group', () => { + const ethnicityGroup = service.createEthnicityInfoGroup(); + expect(ethnicityGroup).toBeTruthy(); + CommonGroups.ETHNICITY_INFO.fields.forEach((eachField) => { + expect(ethnicityGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should create a race info group', () => { + const raceGroup = service.createRaceInfoGroup(); + expect(raceGroup).toBeTruthy(); + CommonGroups.RACE_INFO.fields.forEach((eachField) => { + expect(raceGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should create a gender info group', () => { + const genderGroup = service.createGenderInfoGroup(); + expect(genderGroup).toBeTruthy(); + CommonGroups.GENDER_INFO.fields.forEach((eachField) => { + expect(genderGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'saveHMDAInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + spyOn(applicationWizardService, 'getNextRoute').and.returnValue('foo bar'); + const ethnicityInfo = {}; + const raceInfo = {}; + const genderInfo = {}; + const infoArray = [ethnicityInfo, genderInfo, raceInfo]; + const activatedRoute = {}; + service.saveAndNavigate(infoArray, activatedRoute, BorrowerType.PRIMARY_BORROWER); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('cancelling should always navigate', () => { + spyOn(router, 'navigate'); + spyOn(applicationWizardService, 'getPreviousRoute').and.returnValue('foo bar'); + const activatedRoute = {}; + service.cancelAndNavigate(activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.service.ts new file mode 100644 index 0000000..005a18f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/hmda-info-page.service.ts @@ -0,0 +1,135 @@ +import { Injectable } from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { PageService } from '../../page-service'; +import { BorrowerType } from '../../../../shared/borrower-type'; +import { WizardValidators } from '../../../field-groups/wizard-validators'; +import { HMDA_ETHNICITIES } from '../../../../shared/api-models/api-hmda-enums'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; +import { FormatService } from 'src/app/shared/pipes/format.service'; + +@Injectable({ + providedIn: 'root' +}) +export class HmdaInfoPageService implements PageService { + + public static readonly ETHNICITY_FORM_NAME = 'ethnicityInformation'; + public static readonly GENDER_FORM_NAME = 'genderInformation'; + public static readonly RACE_FORM_NAME = 'raceInformation'; + + private CURRENT_ROUTE = 'eq-hmda'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private formatService: FormatService, + private router: Router, + private formBuilder: FormBuilder) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + group.addControl(HmdaInfoPageService.ETHNICITY_FORM_NAME, this.createEthnicityInfoGroup()); + group.addControl(HmdaInfoPageService.RACE_FORM_NAME, this.createRaceInfoGroup()); + group.addControl(HmdaInfoPageService.GENDER_FORM_NAME, this.createGenderInfoGroup()); + return group; + } + + public createEthnicityInfoGroup(): FormGroup { + const groupValidator = CommonGroups.ETHNICITY_INFO.groupValidator(); + const memberGroup = this.formBuilder.group({}, {validator: groupValidator}); + CommonGroups.ETHNICITY_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, this.formBuilder.control('', null)); + }, this); + return memberGroup; + } + + public createGenderInfoGroup(): FormGroup { + const groupValidator = CommonGroups.ETHNICITY_INFO.groupValidator(); + const memberGroup = this.formBuilder.group({}, {validator: groupValidator}); + CommonGroups.GENDER_INFO.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + // This code is left here due to a suggestion on ticket #1116. + // if (eachField.validatorFn) { + // eachField.validatorFn.forEach((validationFunction) => { + // validators.push(validationFunction()); + // }, this); + // } + memberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + return memberGroup; + } + + public createRaceInfoGroup(): FormGroup { + const groupValidator = CommonGroups.ETHNICITY_INFO.groupValidator(); + const memberGroup = this.formBuilder.group({}, {validator: groupValidator} ); + CommonGroups.RACE_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, this.formBuilder.control('', null)); + }, this); + return memberGroup; + } + + public saveAndNavigate(pageData, route: ActivatedRoute, individual: BorrowerType) { + this.CURRENT_ROUTE = this.getRouteName(); + this.creditApplicationService.saveHMDAInformation(pageData, individual).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public cancelAndNavigate(route: ActivatedRoute) { + this.CURRENT_ROUTE = this.getRouteName(); + const nextRoute = this.applicationWizardService.getPreviousRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + + public getApplicationGroupValues(groupName: string, borrowerType: BorrowerType): any { + return this.applicationWizardService.getIndividualGroupValues(groupName, borrowerType); + } + + public getRouteName(): string { + return this.applicationWizardService.getSimpleRouteName(this.router.url); + } + + public getNextTopicMessage(): string { + return this.applicationWizardService.getNextTopicMessage(this.CURRENT_ROUTE); + } + + public getApplicantName(borrowerType: BorrowerType): string { + return this.formatService.formatTitleCase( + this.applicationWizardService.getApplicantName(borrowerType) + ); + } + + public getRaceFormValues(borrowerType: BorrowerType): any { + return this.getApplicationGroupValues(HmdaInfoPageService.RACE_FORM_NAME, borrowerType); + } + + public getGenderFormValues(borrowerType: BorrowerType): any { + return this.getApplicationGroupValues(HmdaInfoPageService.GENDER_FORM_NAME, borrowerType); + } + + public getEthnicityFormValues(borrowerType: BorrowerType): any { + return this.getApplicationGroupValues(HmdaInfoPageService.ETHNICITY_FORM_NAME, borrowerType); + } + + public determineBorrowerType(route: ActivatedRoute): BorrowerType { + if (route.snapshot.data[0]) { + return route.snapshot.data[0].individual; + } else { + return BorrowerType.PRIMARY_BORROWER; + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/co-borrower-hmda-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/co-borrower-hmda-info-page.spec.ts new file mode 100644 index 0000000..d4d8ae9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/co-borrower-hmda-info-page.spec.ts @@ -0,0 +1,74 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed, async } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoBorrowerHmdaInfoPage } from './co-borrower-hmda-info-page'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + +describe('CoBorrower HMDA Info Page', () => { + + let page: CoBorrowerHmdaInfoPage; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }).compileComponents(); + + page = new CoBorrowerHmdaInfoPage('PAGETITLES.EQUITY.COAPP_HMDA', 'co-applicant-eq-hmda', + [ + CommonGroups.RACE_INFO, + CommonGroups.ETHNICITY_INFO, + CommonGroups.GENDER_INFO + ] + ); + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + + + })); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isCoApplicant').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getFieldValue').and.callThrough().and.returnValue({ category: 'Equity' }); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isCoApplicant).toHaveBeenCalled(); + expect(applicationWizardService.getFieldValue).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isCoApplicant').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getFieldValue').and.callThrough().and.returnValue({ category: 'Credit Card' }); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isCoApplicant).toHaveBeenCalled(); + expect(applicationWizardService.getFieldValue).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + + it('should correctly determine if page is valid ( default valid )', () => { + spyOn(page, 'isValidPage').and.callThrough(); + const errors = page.isValidPage(applicationWizardService, creditApplicationService); + expect(page.isValidPage).toHaveBeenCalled(); + expect(errors.length).toBeLessThanOrEqual(0); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/co-borrower-hmda-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/co-borrower-hmda-info-page.ts new file mode 100644 index 0000000..9280f86 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/co-borrower-hmda-info-page.ts @@ -0,0 +1,22 @@ +import { ApplicationPage } from '../../../../application-page'; +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { LoanCategory } from 'src/app/shared/loan-type/loan-category'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + +export class CoBorrowerHmdaInfoPage extends ApplicationPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + const loanCategory = applicationWizardService.getFieldValue('loanType').category; + const isCoapplicant = applicationWizardService.isCoApplicant(); + return loanCategory === LoanCategory.Equity && isCoapplicant; + } + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + if (applicationWizardService.getFullCreditApplication()) { + return []; + } else { + return ['ERROR']; + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/primary-hmda-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/primary-hmda-info-page.spec.ts new file mode 100644 index 0000000..47d5a58 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/primary-hmda-info-page.spec.ts @@ -0,0 +1,71 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed, async } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { PrimaryHmdaInfoPage } from './primary-hmda-info-page'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + +describe('Primary HMDA Info Page', () => { + + let page: PrimaryHmdaInfoPage; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }).compileComponents(); + + page = new PrimaryHmdaInfoPage('PAGETITLES.EQUITY.HMDA', 'eq-hmda', + [ + CommonGroups.RACE_INFO, + CommonGroups.ETHNICITY_INFO, + CommonGroups.GENDER_INFO + ] + ); + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + + + })); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'getFieldValue').and.callThrough().and.returnValue({ category: 'Equity' }); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.getFieldValue).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'getFieldValue').and.callThrough().and.returnValue({ category: 'Credit Card' }); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.getFieldValue).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + + it('should correctly determine if page is valid ( default valid )', () => { + spyOn(page, 'isValidPage').and.callThrough(); + const errors = page.isValidPage(applicationWizardService, creditApplicationService); + expect(page.isValidPage).toHaveBeenCalled(); + expect(errors.length).toBeLessThanOrEqual(0); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/primary-hmda-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/primary-hmda-info-page.ts new file mode 100644 index 0000000..fd1e297 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/hmda/pages/primary-hmda-info-page.ts @@ -0,0 +1,22 @@ +import { ApplicationPage } from '../../../../application-page'; +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { LoanCategory } from 'src/app/shared/loan-type/loan-category'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; +import {LoanTitle} from '../../../../../shared/loan-type/loan-title'; + +export class PrimaryHmdaInfoPage extends ApplicationPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + return applicationWizardService.getFieldValue('loanType').category === LoanCategory.Equity + && applicationWizardService.getGroupValues('loanType').title !== LoanTitle.HELOC_RateMod; + } + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + if (applicationWizardService.getFullCreditApplication()) { + return []; + } else { + return ['ERROR']; + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.html new file mode 100644 index 0000000..f949586 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.html @@ -0,0 +1,36 @@ +
+
+
+
+

HEADINGS.GET_STARTED

+
+
+
+
+
+ + + + + + +
+
+ +
+ +
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.scss new file mode 100644 index 0000000..307957b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.scss @@ -0,0 +1,7 @@ +.lets-get-started-text { + text-align: left; +} + +.cc-image { + border-radius: 6%; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.spec.ts new file mode 100644 index 0000000..2e7acb8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.spec.ts @@ -0,0 +1,240 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule, FormGroup } from '@angular/forms'; +import { Component, Input, NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { MemberInfoPageService } from './member-info-page.service'; +import { MemberInfoPageComponent } from './member-info-page.component'; +import { PatriotActComponent } from '../../../../shared/patriot-act/patriot-act.component'; +import { WindowService } from '../../../../shared/window.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { CreditApplicationService } from 'src/app/shared/credit-application.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; +import { LoanTitle } from 'src/app/shared/loan-type/loan-title'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { UiSettings } from 'src/app/shared/configuration/ui-settings'; +import { DefaultSettings } from 'src/app/shared/configuration/default-settings.mock'; +import { configureTestSuite } from 'ng-bullet'; +import { MembershipInformation } from 'src/app/loan-wizard/field-groups/member/membership-information'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; + +@Component({ + selector: 'lw-member-info-form', + template: '

employment info form stub

' +}) +class MockMemberInfoFormComponent { + + @Input() + public group: FormGroup; + + constructor() { } + +} + +describe('MemberInfoPageComponent', () => { + let component: MemberInfoPageComponent; + let fixture: ComponentFixture; + let service: MemberInfoPageService; + let windowService: WindowService; + let creditApplicationService: CreditApplicationService; + let configurationService: ConfigurationService; + let applicationWizardService: ApplicationWizardService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + MemberInfoPageComponent, + MockMemberInfoFormComponent, + PatriotActComponent + ], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MemberInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(MemberInfoPageService); + creditApplicationService = TestBed.get(CreditApplicationService); + windowService = TestBed.get(WindowService); + configurationService = TestBed.get(ConfigurationService); + configurationService.saveConfiguration(defaultSettings); + applicationWizardService = TestBed.get(ApplicationWizardService); + const nonCreditLoan = LoanTypeSet.find(loan => loan.title === LoanTitle.HomeEquityFixed) || LoanTypeSet[0]; + applicationWizardService.setLoanType(nonCreditLoan.id); + }); + + it('should create', () => { + spyOn(service, 'getRouteName').and.returnValue('step-1'); + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.pageForm).toBeTruthy(); + expect(component.memberInfoGroup).toBeTruthy(); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + spyOn(service, 'getRouteName').and.returnValue('step-1'); + fixture.detectChanges(); + component.localState.nextDisabled = false; + spyOn(service, 'saveAndNavigate'); + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + }); + + it('should use the window service to print.', () => { + spyOn(service, 'getRouteName').and.returnValue('step-1'); + fixture.detectChanges(); + spyOn(windowService, 'showWindowForId').and.returnValue(undefined); + component.commandShowPage(); + expect(windowService.showWindowForId).toHaveBeenCalled(); + }); + + it('should require add/remove controls to memberInfoGroup if "yes" is chosen for co applicant', () => { + spyOn(service, 'getRouteName').and.returnValue('step-1'); + fixture.detectChanges(); + expect(component.memberInfoGroup.get('coApplicantLivesInMaritalPropertyState')).toBeFalsy(); + component.updateLocalState({ + applicantType: CreditApplicationValues.CHOICE_YES + }); + fixture.detectChanges(); + expect(component.memberInfoGroup.get('coApplicantLivesInMaritalPropertyState')).toBeTruthy(); + component.updateLocalState({ + applicantType: CreditApplicationValues.CHOICE_NO + }); + fixture.detectChanges(); + expect(component.memberInfoGroup.get('coApplicantLivesInMaritalPropertyState')).toBeFalsy(); + }); + + it('should require addtl controls if "yes" is chosen for co applicant', () => { + spyOn(service, 'getRouteName').and.returnValue('step-1'); + fixture.detectChanges(); + expect(component.memberInfoGroup.get('coApplicantLivesInMaritalPropertyState')).toBeFalsy(); + const data = { + applicantType: CreditApplicationValues.CHOICE_NO, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + agreementRequirement: true + }; + component.updateLocalState(data); + component.memberInfoGroup.patchValue(data); + fixture.detectChanges(); + expect(component.memberInfoGroup.valid).toEqual(true); + data.applicantType = CreditApplicationValues.CHOICE_YES; + component.updateLocalState(data); + component.memberInfoGroup.patchValue(data); + component.memberInfoGroup.controls.coApplicantLivesInMaritalPropertyState.updateValueAndValidity(); + fixture.detectChanges(); + expect(component.memberInfoGroup.valid).toEqual(false); + const expandedData = { + coApplicantLivesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + applicantType: CreditApplicationValues.CHOICE_YES, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + confirmJointCredit: CreditApplicationValues.CHOICE_YES, + agreementRequirement: true + }; + component.updateLocalState(expandedData); + component.memberInfoGroup.patchValue(expandedData); + component.memberInfoGroup.controls.coApplicantLivesInMaritalPropertyState.updateValueAndValidity(); + fixture.detectChanges(); + expect(component.memberInfoGroup.valid).toEqual(true); + }); + + it('should command the modal to open when applicant fails to confirm joint credit', async(done) => { + spyOn(service, 'getRouteName').and.returnValue('step-1'); + fixture.detectChanges(); + component.openModal.subscribe(nextValues => { + expect(nextValues).toBe(true); + done(); + }); + const data = { + coApplicantLivesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + applicantType: CreditApplicationValues.CHOICE_YES, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + confirmJointCredit: CreditApplicationValues.CHOICE_NO + }; + component.updateLocalState(data); + fixture.detectChanges(); + }); + + it('should NOT command the modal to open when applicant confirms joint credit', async(done) => { + spyOn(service, 'getRouteName').and.returnValue('step-1'); + fixture.detectChanges(); + component.openModal.subscribe(nextValues => { + expect(nextValues).toBe(false); + done(); + }); + const data = { + coApplicantLivesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + applicantType: CreditApplicationValues.CHOICE_YES, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + confirmJointCredit: CreditApplicationValues.CHOICE_YES + }; + component.updateLocalState(data); + fixture.detectChanges(); + }); + + it('should render existing member data (maximum)', () => { + const memberData: MembershipInformation = { + coApplicantLivesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + applicantType: CreditApplicationValues.CHOICE_YES, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + confirmJointCredit: CreditApplicationValues.CHOICE_NO, + agreementRequirement: true + }; + spyOn(service, 'getRouteName').and.returnValue('step-1'); + spyOn(service, 'getCommonMemberInfoGroupValues').and.returnValue(memberData); + fixture.detectChanges(); + const controlsLength = Object.keys(component.memberInfoGroup.controls).length; + expect(controlsLength).toEqual(6); + expect(component.pageForm.valid).toBe(true); + }); + + it('should render existing member data (minimum)', () => { + const memberData: MembershipInformation = { + coApplicantLivesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + applicantType: CreditApplicationValues.CHOICE_NO, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + confirmJointCredit: CreditApplicationValues.CHOICE_NO, + agreementRequirement: true + }; + spyOn(service, 'getRouteName').and.returnValue('step-1'); + spyOn(service, 'getCommonMemberInfoGroupValues').and.returnValue(memberData); + fixture.detectChanges(); + const controlsLength = Object.keys(component.memberInfoGroup.controls).length; + expect(controlsLength).toEqual(4); + expect(component.pageForm.valid).toBe(true); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.ts new file mode 100644 index 0000000..3e659e7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.component.ts @@ -0,0 +1,158 @@ +import { Component, OnInit, AfterContentChecked, OnDestroy, AfterViewInit, AfterContentInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { MemberInfoPageService } from './member-info-page.service'; +import { ActivatedRoute } from '@angular/router'; +import { WindowService } from '../../../../shared/window.service'; +import { CreditApplication } from '../../../../shared/credit-application'; +import { MembershipInformation } from '../../../field-groups/member/membership-information'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { Subscription, Subject } from 'rxjs'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; + +@Component({ + selector: 'lw-member-info-page', + templateUrl: './member-info-page.component.html', + styleUrls: ['./member-info-page.component.scss'] +}) +export class MemberInfoPageComponent implements OnInit, AfterContentInit, OnDestroy { + + private valueSubscription: Subscription; + private statusSubscription: Subscription; + + public openModal: Subject; + + public pageForm: FormGroup; + public memberInfoGroup: FormGroup; + private readonly FORM_STATE_INVALID = 'INVALID'; + + public amountRequestedName = 'Credit Limit'; + public loanTypeMessageTranslated = ''; + + public askLoanPurposeSignature = false; + + public savingData = null; + + public showCCRewardsCashBackDisclaimer = null; + + public localState = { + nextDisabled: true, + typeMessage: '', + imagePath: null, + nextTopic: '', + hasCoApplicant: false, + isModalOpen: false, + beforeYouBeginMessage: null, + requireAgreement: false + }; + + constructor( + private memberInfoPageService: MemberInfoPageService, + private route: ActivatedRoute, + private applicationWizardService: ApplicationWizardService, + private windowService: WindowService) { } + + public ngOnInit() { + this.openModal = new Subject(); + this.pageForm = this.memberInfoPageService.createPageGroup(); + this.patchMemberInfoGroup(); + this.showRewardsCashBackDisclaimer(); + this.memberInfoPageService.getLoanTypeMessage().subscribe((newMessage) => { + this.localState.typeMessage = newMessage; + }); + } + + public ngAfterContentInit() { + this.pageForm.addControl(MemberInfoPageService.MEMBER_FORM_NAME, this.memberInfoGroup); + this.localState.nextDisabled = this.pageForm.status === this.FORM_STATE_INVALID; + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + } + + public showRewardsCashBackDisclaimer() { + const loanType = this.applicationWizardService.getGroupValues('loanType').apiCode; + if (loanType == LoanCode.CC_CashBack || loanType == LoanCode.CC_Rewards) { + this.showCCRewardsCashBackDisclaimer = {isActive: true}; + } + + return this.showCCRewardsCashBackDisclaimer; + } + + public ngOnDestroy() { + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public updateLocalState(pageValues): void { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + + this.updateCoApplicantFields(pageValues); + this.localState.hasCoApplicant = pageValues.applicantType === CreditApplicationValues.CHOICE_YES; + this.localState.imagePath = this.memberInfoPageService.getImagePath(); + this.localState.nextTopic = this.memberInfoPageService.getNextTopicMessage(); + this.localState.beforeYouBeginMessage = this.memberInfoPageService.getBeforeYouBeginMessage(); + this.localState.requireAgreement = this.memberInfoPageService.requireAgreement(); + + if (pageValues.applicantType === CreditApplicationValues.CHOICE_YES && + pageValues.confirmJointCredit === CreditApplicationValues.CHOICE_NO && + !this.localState.isModalOpen) { + this.localState.isModalOpen = true; + this.openModal.next(true); + } else { + this.localState.isModalOpen = false; + this.openModal.next(false); + } + + this.valueSubscription = this.memberInfoGroup.valueChanges.subscribe((pageValue) => { + this.updateLocalState(pageValue); + }); + } + + public updateCoApplicantFields(pageValues) { + if (pageValues.applicantType === CreditApplicationValues.CHOICE_YES && !this.memberInfoGroup.get('confirmJointCredit')) { + this.memberInfoPageService.addCoApplicantFieldsToMemberGroup(this.memberInfoGroup); + this.memberInfoGroup.updateValueAndValidity(); + } else if (pageValues.applicantType === CreditApplicationValues.CHOICE_NO && this.memberInfoGroup.get('confirmJointCredit')) { + this.memberInfoPageService.removeCoApplicantFieldsFromMemberGroup(this.memberInfoGroup); + this.memberInfoGroup.updateValueAndValidity(); + } + } + + public patchMemberInfoGroup() { + this.memberInfoGroup = this.memberInfoPageService.createMemberInformationGroup(); + const memberInfo: MembershipInformation = this.memberInfoPageService.getCommonMemberInfoGroupValues(); + + if (memberInfo.applicantType !== CreditApplicationValues.CHOICE_YES) { + this.memberInfoPageService.removeCoApplicantFieldsFromMemberGroup(this.memberInfoGroup); + + } + + this.memberInfoGroup.patchValue(memberInfo); + this.updateLocalState(memberInfo); + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + this.memberInfoPageService.saveAndNavigate(this.memberInfoGroup.value, this.route); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + + } + + public commandShowPage(): void { + this.windowService.showWindowForId('electronic-disclosures-agreement-container'); + } + + public changeApplicantTypeToIndividual(): void { + this.localState.hasCoApplicant = false; + this.memberInfoPageService.removeCoApplicantFieldsFromMemberGroup(this.memberInfoGroup); + this.memberInfoGroup.patchValue({applicantType: CreditApplicationValues.CHOICE_NO}); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.service.spec.ts new file mode 100644 index 0000000..7632f9d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.service.spec.ts @@ -0,0 +1,102 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { MemberInfoPageService } from './member-info-page.service'; +import { MembershipInformation } from '../../../field-groups/member/membership-information'; +import { TranslateModule } from '@ngx-translate/core'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { UiSettings } from 'src/app/shared/configuration/ui-settings'; +import { DefaultSettings } from 'src/app/shared/configuration/default-settings.mock'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; +import { LoanTitle } from 'src/app/shared/loan-type/loan-title'; + + +describe('Member Info Page Service', () => { + + let service: MemberInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + let router: Router; + let configurationService: ConfigurationService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(MemberInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + router = TestBed.get(Router); + configurationService = TestBed.get(ConfigurationService); + configurationService.saveConfiguration(defaultSettings); + const nonCreditLoan = LoanTypeSet.find(loan => loan.title === LoanTitle.HomeEquityFixed) || LoanTypeSet[0]; + applicationWizardService.setLoanType(nonCreditLoan.id); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create a member info group', () => { + const memberGroup = service.createMemberInformationGroup(); + expect(memberGroup).toBeTruthy(); + CommonGroups.MEMBER_INFO.fields.forEach((eachField) => { + expect(memberGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should provide the value for the groups', () => { + const testGroupName = 'testGroup'; + spyOn(creditApplicationService, 'getMemberInformation').and.returnValue(true); + service.getCommonMemberInfoGroupValues(); + expect(creditApplicationService.getMemberInformation).toHaveBeenCalled(); + }); + + it('should navigate the application, on successful save', () => { + spyOn(creditApplicationService, 'saveMemberInformation').and.returnValue(of(true)); + spyOn(router, 'navigate'); + const info = {}; + const activatedRoute = { routeConfig: { + path: 'step-1' + }}; + service.saveAndNavigate(info, activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'saveMemberInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + const info = {}; + const activatedRoute = {routeConfig: { + path: 'step-1' + }}; + service.saveAndNavigate(info, activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.service.ts new file mode 100644 index 0000000..a7c0b58 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.service.ts @@ -0,0 +1,134 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormBuilder, FormGroup, ValidatorFn } from '@angular/forms'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { MembershipInformation } from '../../../field-groups/member/membership-information'; +import { PageService } from '../../page-service'; +import { ConfigurationService } from '../../../../shared/configuration/configuration.service'; +import { Observable } from 'rxjs'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; +import { LoanTitle } from 'src/app/shared/loan-type/loan-title'; +import { MemberInfoFormService } from 'src/app/loan-wizard/field-groups/member/member-info-form.service'; +import { LoanCategory } from 'src/app/shared/loan-type/loan-category'; + +@Injectable({ + providedIn: 'root' +}) +export class MemberInfoPageService implements PageService { + + public static readonly MEMBER_FORM_NAME = 'memberInformation'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private configurationService: ConfigurationService, + private router: Router, + private formBuilder: FormBuilder) { + } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + return group; + } + + public createMemberInformationGroup(): FormGroup { + const memberGroup = this.formBuilder.group({}); + const loanType = this.applicationWizardService.getGroupValues('loanType'); + const loanCategory = this.applicationWizardService.getFieldValue('loanType').category; + + if(loanCategory !== LoanCategory.CreditCard){ + CommonGroups.MEMBER_INFO.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + validators.push(validationFunction(loanCategory, loanType, this.configurationService)); + }, this); + } + memberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + }else{ + CommonGroups.MEMBER_INFO_CC.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + memberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + } + this.addCoApplicantFieldsToMemberGroup(memberGroup); + this.addAgreementRequirement(memberGroup); + return memberGroup; + } + + public addCoApplicantFieldsToMemberGroup(formGroup: FormGroup) { + CommonGroups.CO_APP_MEMBER_INFO.fields.forEach((eachField) => { + formGroup.addControl(eachField.field, this.formBuilder.control('', [])); + }, this); + } + + public addAgreementRequirement(formGroup: FormGroup) { + if (this.requireAgreement()) { + const loanType = this.applicationWizardService.getGroupValues('loanType'); + CommonGroups.MEMBER_AGREEMENT_REQUIREMENT.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + validators.push(validationFunction(loanType, this.configurationService)); + }, this); + } + formGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + } + } + + public removeCoApplicantFieldsFromMemberGroup(formGroup: FormGroup) { + formGroup.removeControl('confirmJointCredit'); + formGroup.removeControl('coApplicantLivesInMaritalPropertyState'); + } + + public getCommonMemberInfoGroupValues(): MembershipInformation { + return this.creditApplicationService.getMemberInformation(); + } + + public saveAndNavigate(memberInfoData: MembershipInformation, route: ActivatedRoute) { + + this.creditApplicationService.saveMemberInformation(memberInfoData).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(route.routeConfig.path); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(route.routeConfig.path); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public getImagePath(): string { + return this.applicationWizardService.getImagePath(); + } + + public getLoanTypeMessage(): Observable { + return this.applicationWizardService.getLoanMessage(); + } + + public getRouteName(): string { + return this.applicationWizardService.getSimpleRouteName(this.router.url); + } + + public getNextTopicMessage(): string { + return this.applicationWizardService.getNextTopicMessage(this.getRouteName()); + } + + public getBeforeYouBeginMessage(): object { + return this.applicationWizardService.getBeforeYouBeginMessage(); + } + + public requireAgreement(): boolean { + var loanTitle = this.applicationWizardService.getGroupValues('loanType').title; + return loanTitle && ![LoanTitle.Vehicle, LoanTitle.VehiclePreApproval, LoanTitle.HELOC_RateMod].includes(loanTitle); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.ts new file mode 100644 index 0000000..7f17505 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/member/member-info-page.ts @@ -0,0 +1,22 @@ +import { ApplicationPage } from '../../../application-page'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { MembershipInformation } from '../../../field-groups/member/membership-information'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +export class MemberInfoPage extends ApplicationPage { + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + let value; + const errors = []; + const memberInfo: MembershipInformation = creditApplicationService.getMemberInformation(); + this.fieldgroup.forEach((pageFieldGroup) => { + pageFieldGroup.fields.forEach((pageField) => { + value = memberInfo[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }, this); + }, this); + return errors; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-personal-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-personal-info-page.spec.ts new file mode 100644 index 0000000..d071a04 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-personal-info-page.spec.ts @@ -0,0 +1,61 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoBorrowerPersonalInfoPage } from './co-borrower-personal-info-page'; +import { configureTestSuite } from 'ng-bullet'; + +describe('CoBorrower Personal Info Page', () => { + + let page: CoBorrowerPersonalInfoPage; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new CoBorrowerPersonalInfoPage('PAGETITLES.COMMON.COAPP_PERSONAL_INFO', + 'co-applicant-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isCoApplicant').and.callThrough().and.returnValue(true); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isCoApplicant).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isCoApplicant').and.callThrough().and.returnValue(false); + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isCoApplicant).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-personal-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-personal-info-page.ts new file mode 100644 index 0000000..b0f30a8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-personal-info-page.ts @@ -0,0 +1,15 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { PrimaryPersonalInfoPage } from './primary-personal-info-page'; + +export class CoBorrowerPersonalInfoPage extends PrimaryPersonalInfoPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + return applicationWizardService.isCoApplicant(); + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.CO_BORROWER; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-spouse-personal-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-spouse-personal-info-page.spec.ts new file mode 100644 index 0000000..30b90c2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-spouse-personal-info-page.spec.ts @@ -0,0 +1,68 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed, async } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoBorrowerSpousePersonalInfoPage } from './co-borrower-spouse-personal-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; +import { configureTestSuite } from 'ng-bullet'; + +describe('CoBorrower Spouse Personal Info Page', () => { + + let page: CoBorrowerSpousePersonalInfoPage; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new CoBorrowerSpousePersonalInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_PERSONAL_INFO', + 'co-applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.FullInfoSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.TattleSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-spouse-personal-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-spouse-personal-info-page.ts new file mode 100644 index 0000000..bf3938e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/co-borrower-spouse-personal-info-page.ts @@ -0,0 +1,18 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { BorrowerType } from '../../../../../shared/borrower-type'; +import { PrimaryPersonalInfoPage } from './primary-personal-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; + +export class CoBorrowerSpousePersonalInfoPage extends PrimaryPersonalInfoPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + if (applicationWizardService.isApplicantNonSpouse(this.getBorrowerType())) { + return applicationWizardService.getSpouseType(this.getBorrowerType()) === SpouseType.FullInfoSpouse; + } + return false; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.CO_BORROWER; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-personal-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-personal-info-page.spec.ts new file mode 100644 index 0000000..f950b14 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-personal-info-page.spec.ts @@ -0,0 +1,63 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { PrimaryPersonalInfoPage } from './primary-personal-info-page'; +import { BorrowerType } from '../../../../../shared/borrower-type'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + +describe('Primary Personal Info Page', () => { + + let page: PrimaryPersonalInfoPage; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new PrimaryPersonalInfoPage('PAGETITLES.COMMON.PERSONAL_INFO', 'step-2', + [ + CommonGroups.PERSONAL_INFO + ] + ); + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should always return primary borrower type', () => { + spyOn(page, 'getBorrowerType').and.callThrough(); + const borrowerType = page.getBorrowerType(); + expect(page.getBorrowerType).toHaveBeenCalled(); + expect(borrowerType).toBe(BorrowerType.PRIMARY_BORROWER); + }); + + it('should correctly determine if page is valid ( default invalid )', () => { + spyOn(page, 'isValidPage').and.callThrough(); + const errors = page.isValidPage(applicationWizardService, creditApplicationService); + expect(page.isValidPage).toHaveBeenCalled(); + expect(errors.length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-personal-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-personal-info-page.ts new file mode 100644 index 0000000..f6cf025 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-personal-info-page.ts @@ -0,0 +1,30 @@ +import { ApplicationPage } from '../../../../application-page'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { PersonalInformation } from '../../../../field-groups/personal/personal-information'; +import { BorrowerType } from '../../../../../shared/borrower-type'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + +export class PrimaryPersonalInfoPage extends ApplicationPage { + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + let value; + const errors = []; + const borrowerType = this.getBorrowerType(); + + const personalInfo: PersonalInformation = applicationWizardService + .getIndividualGroupValues('personalInformation', borrowerType); + this.fieldgroup.forEach((pageFieldGroup) => { + pageFieldGroup.fields.forEach((pageField) => { + value = personalInfo[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }, this); + }, this); + return errors; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.PRIMARY_BORROWER; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-spouse-personal-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-spouse-personal-info-page.spec.ts new file mode 100644 index 0000000..a751e96 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-spouse-personal-info-page.spec.ts @@ -0,0 +1,68 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { PrimarySpousePersonalInfoPage } from './primary-spouse-personal-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Primary Spouse Personal Info Page', () => { + + let page: PrimarySpousePersonalInfoPage; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new PrimarySpousePersonalInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_PERSONAL_INFO', + 'applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.FullInfoSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.TattleSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-spouse-personal-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-spouse-personal-info-page.ts new file mode 100644 index 0000000..2d1aaba --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/pages/primary-spouse-personal-info-page.ts @@ -0,0 +1,19 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { BorrowerType } from '../../../../../shared/borrower-type'; +import { PrimaryPersonalInfoPage } from './primary-personal-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; + +export class PrimarySpousePersonalInfoPage extends PrimaryPersonalInfoPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + if (applicationWizardService.isApplicantNonSpouse(this.getBorrowerType())) { + return applicationWizardService.getSpouseType(this.getBorrowerType()) === SpouseType.FullInfoSpouse; + } + return false; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.PRIMARY_BORROWER; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.html new file mode 100644 index 0000000..6f8b500 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.html @@ -0,0 +1,29 @@ +
+
+
+

HEADINGS.PERSONAL.{{localState.borrowerType}}

+
+
COPY.PERSONAL.{{localState.borrowerType}}
+
+
+ + + + +
+ + +
+ + +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.spec.ts new file mode 100644 index 0000000..7708db6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.spec.ts @@ -0,0 +1,206 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { PersonalInfoPageComponent } from './personal-info-page.component'; +import { PersonalInfoPageService } from './personal-info-page.service'; +import { PersonalInfoFormComponent } from '../../../field-groups/personal/personal-info-form.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { PersonalInformation } from '../../../field-groups/personal/personal-information'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationValues } from 'src/app/shared/credit-application-values'; +import { PhoneTypeEnum } from 'src/app/shared/api-models/api-enums'; +import { ApiSuffix } from 'src/app/shared/api-models/api-suffix'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; + + +describe('PersonalInfoPageComponent', () => { + let component: PersonalInfoPageComponent; + let fixture: ComponentFixture; + let service: PersonalInfoPageService; + let applicationWizardService: ApplicationWizardService; + const personalInformationNoMarriage: PersonalInformation = { + firstName: 'John', + middleInitial: 'J', + lastName: 'Doe', + dateOfBirth: '12/02/1945', + suffix: ApiSuffix.IV, + socialSecurityNumber: '999-99-0000', + primaryPhoneNumberType: PhoneTypeEnum.Home, + primaryPhoneNumber: '414-888-9999', + email: 'noone@nowhere.org', + secondaryPhoneNumber: '', + secondaryPhoneNumberType: null, + primaryApplicantEmail: null, + coApplicantEmail: null, + files: null, + isUploading: null + }; + + const personalInformationMarriage: PersonalInformation = { + firstName: 'John', + middleInitial: 'J', + lastName: 'Doe', + dateOfBirth: '12/02/1945', + suffix: null, + socialSecurityNumber: '999-99-0000', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + primaryPhoneNumber: '414-888-9999', + email: 'noone@nowhere.org', + secondaryPhoneNumber: '', + secondaryPhoneNumberType: null, + maritalStatus: CreditApplicationValues.CHOICE_YES, + primaryApplicantEmail: null, + coApplicantEmail: null, + files: null, + isUploading: null + }; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + PersonalInfoPageComponent, + PersonalInfoFormComponent + ], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PersonalInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(PersonalInfoPageService); + spyOn(service, 'getNextTopicMessage').and.callThrough().and.returnValue('foo bar'); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + spyOn(service, 'getRouteName').and.returnValue('step-2'); + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.pageForm).toBeTruthy(); + expect(component.personalInfoGroup).toBeTruthy(); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + expect(component.commandPrevious).not.toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + spyOn(service, 'getRouteName').and.returnValue('step-2'); + fixture.detectChanges(); + spyOn(service, 'saveAndNavigate'); + component.localState.nextDisabled = false; + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + spyOn(service, 'cancelAndNavigate'); + component.commandPrevious(); + expect(service.cancelAndNavigate).toHaveBeenCalled(); + }); + + it('should bind the previous button to the correct command function', () => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonPrevious')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).not.toHaveBeenCalled(); + expect(component.commandPrevious).toHaveBeenCalled(); + }); + }); + + it('should require secondary phone type if phone is present.', () => { + spyOn(service, 'getRouteName').and.returnValue('step-1'); + fixture.detectChanges(); + component.personalInfoGroup.patchValue({ + 'secondaryPhoneNumber': '', + 'secondaryPhoneNumberType': '' + }); + fixture.detectChanges(); + expect(component.personalInfoGroup.get('secondaryPhoneNumberType').valid).toEqual(true, 'Failed on both empty'); + component.personalInfoGroup.patchValue({ + 'secondaryPhoneNumber': '(414) 222-3333', + 'secondaryPhoneNumberType': '' + }); + expect(component.personalInfoGroup.get('secondaryPhoneNumberType').valid).toEqual(false, 'Failed on phone without type'); + component.personalInfoGroup.patchValue({ + 'secondaryPhoneNumber': '(414) 555-6666', + 'secondaryPhoneNumberType': '2' + }); + expect(component.personalInfoGroup.get('secondaryPhoneNumberType').valid).toEqual(true, 'Failed on phone with type'); + component.personalInfoGroup.patchValue({ + 'secondaryPhoneNumber': '', + 'secondaryPhoneNumberType': '2' + }); + expect(component.personalInfoGroup.get('secondaryPhoneNumberType').valid).toEqual(true, 'Failed on empty secondary phone number'); + }); + + it('Should render existing personal data (no marriage)', () => { + spyOn(service, 'getApplicationGroupValues').and.returnValue(personalInformationNoMarriage); + spyOn(service, 'shouldAskMaritalStatus').and.returnValue(false); + spyOn(service, 'hasCoApplicant').and.returnValue(false); + + fixture.detectChanges(); + expect(service.getApplicationGroupValues).toHaveBeenCalled(); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.personalInfoGroup).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.personalInfoGroup.value).toEqual(personalInformationNoMarriage); + }); + + it('should render existing personal data (marriage)', () => { + spyOn(service, 'getApplicationGroupValues').and.returnValue(personalInformationMarriage); + spyOn(service, 'shouldAskMaritalStatus').and.returnValue(true); + spyOn(service, 'hasCoApplicant').and.returnValue(true); + spyOn(service, 'getIsCoApplicantSpouse').and.returnValue(CreditApplicationValues.CHOICE_YES); + fixture.detectChanges(); + expect(service.getApplicationGroupValues).toHaveBeenCalled(); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.personalInfoGroup).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.personalInfoGroup.value).toEqual( + Object.assign(personalInformationMarriage, {coApplicantSpouse: CreditApplicationValues.CHOICE_YES}) + ); + }); + + it('should not have form fields for marital and co-applicant in some cases', () => { + spyOn(service, 'getApplicationGroupValues').and.returnValue(personalInformationMarriage); + spyOn(service, 'shouldAskMaritalStatus').and.returnValue(false); + spyOn(service, 'hasCoApplicant').and.returnValue(false); + fixture.detectChanges(); + expect(service.getApplicationGroupValues).toHaveBeenCalled(); + expect(component.pageForm.valid).toBeTruthy(); + expect(component.personalInfoGroup).toBeTruthy(); + expect(component.personalInfoGroup.get('maritalStatus')).toBeFalsy(); + expect(component.personalInfoGroup.get('coApplicantSpouse')).toBeFalsy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.ts new file mode 100644 index 0000000..bdd3f5d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.component.ts @@ -0,0 +1,126 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { PersonalInfoPageService } from './personal-info-page.service'; +import { ActivatedRoute } from '@angular/router'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'lw-personal-info-page', + templateUrl: './personal-info-page.component.html' +}) +export class PersonalInfoPageComponent implements OnInit, OnDestroy { + + public pageForm: FormGroup; + public personalInfoGroup: FormGroup; + private readonly FORM_STATE_INVALID = 'INVALID'; + private valueSubscription: Subscription; + private statusSubscription: Subscription; + + public savingData = null; + + public showProofOfStudentUploadForm = false; + public borrowerType: string; + public loanId: string; + + public localState = { + nextDisabled: true, + previousDisabled: false, + borrowerType: BorrowerType.PRIMARY_BORROWER, + nextTopic: '', + shouldAskMaritalStatus: false, + // This 'hasCoApplicant' bool is only relevant to the PRIMARY_BORROWER + hasCoApplicant: false, + showSpouseDisclosure: true, + isHelocRateMod: false + }; + + constructor( + private personalInfoPageService: PersonalInfoPageService, + private route: ActivatedRoute) {} + + public ngOnInit() { + this.initializeFormGroups(); + this.updateLocalState(this.pageForm.value); + this.showProofOfStudentUploadForm = this.personalInfoPageService.isStudent(); + this.borrowerType = this.personalInfoPageService.determineBorrowerType(this.route).toString(); + this.loanId = this.personalInfoPageService.getLoanId(); + } + + public initializeFormGroups() { + const borrowerType = this.personalInfoPageService.determineBorrowerType(this.route); + this.pageForm = this.personalInfoPageService.createPageGroup(); + this.personalInfoGroup = this.pageForm.get(PersonalInfoPageService.PERSONAL_FORM_NAME); + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + this.personalInfoGroup.patchValue( + this.personalInfoPageService.getApplicationGroupValues(PersonalInfoPageService.PERSONAL_FORM_NAME, borrowerType)); + } + + public updateLocalState(nextValues: any) { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + const borrowerType = this.personalInfoPageService.determineBorrowerType(this.route); + this.localState = { + nextDisabled: this.localState.nextDisabled, + previousDisabled: false, + borrowerType: borrowerType, + nextTopic: this.personalInfoPageService.getNextTopicMessage(), + shouldAskMaritalStatus: this.personalInfoPageService.shouldAskMaritalStatus(borrowerType), + hasCoApplicant: this.personalInfoPageService.hasCoApplicant() && borrowerType === BorrowerType.PRIMARY_BORROWER, // move to service + showSpouseDisclosure: borrowerType === BorrowerType.PRIMARY_BORROWER_SPOUSE || borrowerType === BorrowerType.CO_BORROWER_SPOUSE, + isHelocRateMod: this.personalInfoPageService.isHelocRateMod() + }; + this.adjustPersonalForms(this.personalInfoGroup.value); + this.valueSubscription = this.pageForm.valueChanges.subscribe((value) => { + this.updateLocalState(value); + }); + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + + this.personalInfoPageService.saveAndNavigate(this.personalInfoGroup.value, this.route, this.localState.borrowerType); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + } + + public commandPrevious() { + this.personalInfoPageService.cancelAndNavigate(this.route); + } + + public adjustPersonalForms(value: any): void { + if (this.localState.shouldAskMaritalStatus) { + this.personalInfoPageService.addMarriageControlsIfMissing(this.personalInfoGroup, this.localState.borrowerType); + } else { + this.personalInfoPageService.removeMarriageControls(this.personalInfoGroup); + } + if (this.shouldShowSpouseQuestions()) { + this.personalInfoPageService.addSpouseControlsIfMissing(this.personalInfoGroup, this.localState.borrowerType); + } else { + this.personalInfoPageService.removeSpouseControls(this.personalInfoGroup); + } + } + + public shouldShowSpouseQuestions() { + return this.localState.shouldAskMaritalStatus && this.localState.hasCoApplicant && this.hasAnsweredMarried(); + } + + public hasAnsweredMarried() { + return this.personalInfoGroup.value.maritalStatus === CreditApplicationValues.CHOICE_YES; + } + + public ngOnDestroy() { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.service.spec.ts new file mode 100644 index 0000000..67150a6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.service.spec.ts @@ -0,0 +1,121 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { MockActivatedRoute } from '../../../../testing/shared-mocks/activated-route.mock'; +import { PersonalInfoPageService } from './personal-info-page.service'; +import { PersonalInformation } from '../../../field-groups/personal/personal-information'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + + +describe('Personal Info Page Service', () => { + + let service: PersonalInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let creditApplicationService: CreditApplicationService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(PersonalInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + creditApplicationService = TestBed.get(CreditApplicationService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create the top level form group', () => { + spyOn(service, 'createPersonalInformationGroup').and.callThrough(); + service.createPageGroup(); + expect(service.createPersonalInformationGroup).toHaveBeenCalled(); + }); + + it('should create a personal info group', () => { + const personalGroup = service.createPersonalInformationGroup(); + expect(personalGroup).toBeTruthy(); + CommonGroups.PERSONAL_INFO.fields.forEach((eachField) => { + expect(personalGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should provide the value for the groups', () => { + const testGroupName = 'testGroup'; + spyOn(applicationWizardService, 'getIndividualGroupValues').and.returnValue(true); + service.getApplicationGroupValues(testGroupName, BorrowerType.PRIMARY_BORROWER); + expect(applicationWizardService.getIndividualGroupValues).toHaveBeenCalledWith(testGroupName, BorrowerType.PRIMARY_BORROWER); + }); + + it('should navigate the application, on successful save', () => { + spyOn(creditApplicationService, 'savePersonalInformation').and.returnValue(of(true)); + spyOn(router, 'navigate'); + spyOn(applicationWizardService, 'getNextRoute').and.returnValue('foo bar'); + const info = {}; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute, BorrowerType.PRIMARY_BORROWER); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'savePersonalInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + spyOn(applicationWizardService, 'getNextRoute').and.returnValue('foo bar'); + const info = {}; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute, BorrowerType.PRIMARY_BORROWER); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('cancelling should always navigate', () => { + spyOn(router, 'navigate'); + const activatedRoute = {}; + service.cancelAndNavigate(activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should mark waypoint complete upon successful save', async() => { + spyOn(applicationWizardService, 'getNextRoute').and.returnValue('../step-3'); + spyOn(creditApplicationService, 'savePersonalInformation').and.returnValue(of(true)); + spyOn(creditApplicationDaoService, 'saveApplication').and.returnValue(of(true)); + const info = {}; + const activatedRoute = new MockActivatedRoute({}); + service.saveAndNavigate(info, activatedRoute, BorrowerType.PRIMARY_BORROWER); + }); + + it('should always ask marital status for PRIMARY and CO borrowers', () => { + expect(service.shouldAskMaritalStatus(BorrowerType.CO_BORROWER)).toBe(true); + expect(service.shouldAskMaritalStatus(BorrowerType.PRIMARY_BORROWER)).toBe(true); + expect(service.shouldAskMaritalStatus(BorrowerType.CO_BORROWER_SPOUSE)).toBe(false); + expect(service.shouldAskMaritalStatus(BorrowerType.PRIMARY_BORROWER_SPOUSE)).toBe(false); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.service.ts new file mode 100644 index 0000000..e558f70 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/personal/personal-info-page.service.ts @@ -0,0 +1,152 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { PersonalInformation } from '../../../field-groups/personal/personal-information'; +import { PageService } from '../../page-service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; +import {LoanTitle} from '../../../../shared/loan-type/loan-title'; + +@Injectable({ + providedIn: 'root' +}) +export class PersonalInfoPageService implements PageService { + + public static readonly PERSONAL_FORM_NAME = 'personalInformation'; + + private CURRENT_ROUTE = 'step-2'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private router: Router, + private formBuilder: FormBuilder) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + + const personalGroup = this.createPersonalInformationGroup(); + group.addControl(PersonalInfoPageService.PERSONAL_FORM_NAME, personalGroup); + + return group; + } + + public createPersonalInformationGroup(): FormGroup { + const personalGroup = this.formBuilder.group({}); + + CommonGroups.PERSONAL_INFO.fields.forEach((eachField) => { + personalGroup.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }, this); + + return personalGroup; + } + + public getApplicationGroupValues(groupName: string, individual: BorrowerType): any { + return this.applicationWizardService.getIndividualGroupValues(groupName, individual); + } + + public getIsCoApplicantSpouse(): string { + return this.applicationWizardService.getFullCreditApplication().hasCoApplicantSpouse; + } + + public isHelocRateMod(): boolean { + return this.applicationWizardService.getGroupValues('loanType').title === LoanTitle.HELOC_RateMod; + } + + public isStudent(): boolean { + return this.applicationWizardService.getGroupValues('loanType').title === LoanTitle.Student; + } + + public getLoanId(): string { + return this.applicationWizardService.getGroupValues('loanId'); + } + + public saveAndNavigate(personalInfoData: PersonalInformation, route: ActivatedRoute, individual: BorrowerType) { + this.CURRENT_ROUTE = this.getRouteName(); + this.creditApplicationService.savePersonalInformation(personalInfoData, individual).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public cancelAndNavigate(route: ActivatedRoute) { + const nextRoute = this.applicationWizardService.getPreviousRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + + public getRouteName(): string { + return this.applicationWizardService.getSimpleRouteName(this.router.url); + } + + public getNextTopicMessage() { + this.CURRENT_ROUTE = this.getRouteName(); + return this.applicationWizardService.getNextTopicMessage(this.CURRENT_ROUTE); + } + + public shouldAskMaritalStatus(individual: BorrowerType): boolean { + return individual === BorrowerType.PRIMARY_BORROWER || individual === BorrowerType.CO_BORROWER; + } + + public hasCoApplicant(): boolean { + return this.applicationWizardService.getGroupValues('hasCoApplicant') === CreditApplicationValues.CHOICE_YES; + } + + public determineBorrowerType(route: ActivatedRoute): BorrowerType { + if (route.snapshot.data[0]) { + return route.snapshot.data[0].individual; + } + return BorrowerType.PRIMARY_BORROWER; + } + + public addMarriageControlsIfMissing(personalInfoGroup: FormGroup, individual: BorrowerType) { + if (personalInfoGroup.get(CommonGroups.IS_MARRIED.fields[0].field)) { + return; + } + const existingValue = this.getApplicationGroupValues(PersonalInfoPageService.PERSONAL_FORM_NAME, individual); + CommonGroups.IS_MARRIED.fields.forEach((eachField) => { + personalInfoGroup.addControl(eachField.field, this.formBuilder.control(existingValue[eachField.field], eachField.validation.slice())); + }, this); + } + + public removeMarriageControls(personalInfoGroup: FormGroup): void { + if (!personalInfoGroup.get(CommonGroups.IS_MARRIED.fields[0].field)) { + return; + } + CommonGroups.IS_MARRIED.fields.forEach((eachField) => { + personalInfoGroup.removeControl(eachField.field); + }, this); + } + + public addSpouseControlsIfMissing(personalInfoGroup: FormGroup, individual: BorrowerType) { + if (personalInfoGroup.get(CommonGroups.IS_CO_APPLICANT_SPOUSE.fields[0].field)) { + return; + } + + const existingValue = this.getIsCoApplicantSpouse(); + CommonGroups.IS_CO_APPLICANT_SPOUSE.fields.forEach((eachField) => { + personalInfoGroup.addControl(eachField.field, this.formBuilder.control(existingValue, eachField.validation.slice())); + }, this); + } + + public removeSpouseControls(personalInfoGroup: FormGroup): void { + if (!personalInfoGroup.get(CommonGroups.IS_CO_APPLICANT_SPOUSE.fields[0].field)) { + return; + } + CommonGroups.IS_CO_APPLICANT_SPOUSE.fields.forEach((eachField) => { + personalInfoGroup.removeControl(eachField.field); + }, this); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.html new file mode 100644 index 0000000..3d34db3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.html @@ -0,0 +1,19 @@ +
+
+

HEADINGS.PROCESSING

+
+
+
+ +
+
+
+
+
+ + {{dStep.getMessage()}} +
+
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.scss new file mode 100644 index 0000000..8b35f22 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.scss @@ -0,0 +1,25 @@ + +$baseColor : #e74c3c; +$fontColor : #fff; +$max-width: 770px; + +.decision-row { + margin-bottom: 0.25em; + color: #DFDFDF; + font-size: 44px; +} + +.active { + color: #005B9A; +} + +.done { + color: #7ca550; +} + +/*** Mobile view settings ***/ +@media screen and (max-width: $max-width) { + .decision-row { + font-size: 24px; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.spec.ts new file mode 100644 index 0000000..5a2e6b7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.spec.ts @@ -0,0 +1,127 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { ProcessingPageComponent } from './processing-page.component'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { ProcessingPageService } from './processing-page.service'; +import { DecisionAnimationComponent } from '../../../../layout/decision-animation/decision-animation.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { ConfigurationService } from '../../../../shared/configuration/configuration.service'; +import { MockConfigurationService } from '../../../../testing/shared-mocks/configuration.mock.service'; +import { configureTestSuite } from 'ng-bullet'; +import { of } from 'rxjs'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; + +describe('ProcessingPageComponent', () => { + + let component: ProcessingPageComponent; + let fixture: ComponentFixture; + let processingPageService: ProcessingPageService; + let applicationWizardService: ApplicationWizardService; + let configurationService: ConfigurationService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ProcessingPageComponent, + DecisionAnimationComponent + ], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService}, + {provide: ConfigurationService, useValue: MockConfigurationService} + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProcessingPageComponent); + component = fixture.componentInstance; + configurationService = TestBed.get(ConfigurationService); + processingPageService = TestBed.get(ProcessingPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + spyOn(component, 'reviewApplication').and.returnValue(null); + spyOn(component, 'getPhoneNumber').and.returnValue('414-555-5309'); + fixture.detectChanges(); + expect(component.reviewApplication).toHaveBeenCalled(); + }); + + it('should review the application and start the next step', () => { + spyOn(component, 'getPhoneNumber').and.returnValue('414-555-5309'); + spyOn(processingPageService, 'reviewApplication').and.returnValue(of(true)); + spyOn(component, 'transmitApplication').and.returnValue(null); + fixture.detectChanges(); + expect(processingPageService.reviewApplication).toHaveBeenCalled(); + expect(component.transmitApplication).toHaveBeenCalled(); + // component.reviewApplication(); + expect(component.processingSteps[component.STEP_REVIEW].isComplete).toEqual(true); + }); + + it('should transmit the application and start the next step', () => { + spyOn(component, 'getPhoneNumber').and.returnValue('414-555-5309'); + spyOn(processingPageService, 'reviewApplication').and.returnValue(of(true)); + spyOn(processingPageService, 'submitApplication').and.returnValue(of(true)); + spyOn(component, 'submitDecision').and.returnValue(null); + + fixture.detectChanges(); + expect(processingPageService.reviewApplication).toHaveBeenCalled(); + expect(processingPageService.submitApplication).toHaveBeenCalled(); + expect(component.submitDecision).toHaveBeenCalled(); + expect(component.processingSteps[component.STEP_REVIEW].isComplete).toEqual(true); + expect(component.processingSteps[component.STEP_TRANSMIT].isComplete).toEqual(true); + }); + + it('should submit for decision and start the next step', () => { + spyOn(component, 'getPhoneNumber').and.returnValue('414-555-5309'); + spyOn(processingPageService, 'reviewApplication').and.returnValue(of(true)); + spyOn(processingPageService, 'submitApplication').and.returnValue(of(true)); + spyOn(processingPageService, 'submitForDecisioning').and.returnValue(of(true)); + spyOn(component, 'pollDecision').and.returnValue(null); + + fixture.detectChanges(); + expect(processingPageService.reviewApplication).toHaveBeenCalled(); + expect(processingPageService.submitApplication).toHaveBeenCalled(); + expect(processingPageService.submitForDecisioning).toHaveBeenCalled(); + expect(component.pollDecision).toHaveBeenCalled(); + expect(component.processingSteps[component.STEP_REVIEW].isComplete).toEqual(true); + expect(component.processingSteps[component.STEP_TRANSMIT].isComplete).toEqual(true); + expect(component.processingSteps[component.STEP_SUBMIT_DECIDE].isComplete).toEqual(true); + }); + + it('should handle skip Decision', () => { + spyOn(component, 'getPhoneNumber').and.returnValue('414-555-5309'); + spyOn(processingPageService, 'reviewApplication').and.returnValue(of(true)); + spyOn(processingPageService, 'submitApplication').and.returnValue(of(true)); + spyOn(processingPageService, 'setAsSkippedDecision').and.returnValue(of(true)); + spyOn(processingPageService, 'shouldRequestDecision').and.returnValue(false); + spyOn(component, 'pollDecision').and.returnValue(null); + + fixture.detectChanges(); + expect(processingPageService.reviewApplication).toHaveBeenCalled(); + expect(processingPageService.submitApplication).toHaveBeenCalled(); + expect(processingPageService.setAsSkippedDecision).toHaveBeenCalled(); + expect(component.pollDecision).toHaveBeenCalled(); + expect(component.processingSteps[component.STEP_REVIEW].isComplete).toEqual(true); + expect(component.processingSteps[component.STEP_TRANSMIT].isComplete).toEqual(true); + expect(component.processingSteps[component.STEP_SUBMIT_DECIDE].isComplete).toEqual(true); + }); + + it('should provide the decision step array', () => { + const steps = component.createProcessingSteps(); + expect(steps).toBeTruthy(); + expect(steps.length).toEqual(4); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.ts new file mode 100644 index 0000000..a6d0856 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.component.ts @@ -0,0 +1,128 @@ +import { Component, ErrorHandler, OnInit } from '@angular/core'; +import { ProcessingPageService } from './processing-page.service'; +import { ProcessingStep } from './processing-step'; +import { ConfigurationService } from '../../../../shared/configuration/configuration.service'; +import { ActivatedRoute } from '@angular/router'; +import { DecisionState } from '../../../../shared/decision/decision-status'; + +@Component({ + selector: 'lw-processing-page', + templateUrl: './processing-page.component.html', + styleUrls: ['./processing-page.component.scss'] +}) +export class ProcessingPageComponent implements OnInit { + + public STEP_REVIEW = 0; + public STEP_TRANSMIT = 1; + public STEP_SUBMIT_DECIDE = 2; + public STEP_POLL_DECIDE = 3; + + public localState = { + firstName: '', + phoneNumber: '' + }; + public processingSteps: Array = []; + + constructor( + private processingPageService: ProcessingPageService, + private configurationService: ConfigurationService, + private route: ActivatedRoute, + private eHandler: ErrorHandler + ) { } + + public ngOnInit(): void { + this.processingSteps = this.createProcessingSteps(); + this.localState.firstName = this.processingPageService.getBorrowerFirstName(); + this.localState.phoneNumber = this.getPhoneNumber(); + this.reviewApplication(); + } + + public getPhoneNumber(): string { + return this.configurationService.getConfigurationByField('contact')['HelpPhone']; + } + + public setCurrentStep(newStep: number): void { + this.processingSteps.forEach((step, stepIndex) => { + step.setCurrent(stepIndex === newStep); + }, this); + } + + public reviewApplication(): void { + this.setCurrentStep(this.STEP_REVIEW); + this.processingPageService.reviewApplication().subscribe( + (successFlag) => { + if (successFlag) { + this.processingSteps[this.STEP_REVIEW].setComplete(true); + this.transmitApplication(); + } else { + this.handleFailure('Failed to verify application.', DecisionState.NotStarted); + } + } + ); + } + + public transmitApplication(): void { + this.setCurrentStep(this.STEP_TRANSMIT); + this.processingPageService.submitApplication().subscribe( + (successFlag) => { + if (successFlag) { + this.processingSteps[this.STEP_TRANSMIT].setComplete(true); + this.submitDecision(); + } else { + this.handleFailure('Failed to transmit application.', DecisionState.ErrorSubmit); + } + } + ); + } + + public submitDecision(): void { + this.setCurrentStep(this.STEP_SUBMIT_DECIDE); + let decisionObservable = null; + if (this.processingPageService.shouldRequestDecision()) { + decisionObservable = this.processingPageService.submitForDecisioning(); + } else { + decisionObservable = this.processingPageService.setAsSkippedDecision(); + } + decisionObservable.subscribe( + (successFlag) => { + if (successFlag) { + this.processingSteps[this.STEP_SUBMIT_DECIDE].setComplete(true); + this.pollDecision(); + } else { + this.handleFailure('Failed submitting decision.', DecisionState.ErrorDecide); + } + } + ); + } + + public pollDecision(): void { + this.setCurrentStep(this.STEP_POLL_DECIDE); + this.processingPageService.requestDecisionResultFromCRIF().subscribe( + (successFlag) => { + if (successFlag) { + this.processingSteps[this.STEP_POLL_DECIDE].setComplete(true); + this.processingPageService.saveAndNavigate(this.route); + } else { + this.handleFailure('Failure polling decision.', DecisionState.ErrorDecide); + } + }, (error) => { + this.handleFailure('Error polling decision.', DecisionState.ErrorDecide, error); + } + ); + } + + public handleFailure(failure: string, decisionState: DecisionState, error?: any): void { + // @TODO ERROR_NOTIFY + this.processingPageService.setAsErrorDecision(decisionState); + this.processingPageService.saveAndNavigate(this.route); + } + + public createProcessingSteps(): Array { + return [ + new ProcessingStep('Checking Application', 'Application Completed'), + new ProcessingStep('Transmitting Application', 'Application Transmitted'), + new ProcessingStep('Processing Application', 'Application Processed'), + new ProcessingStep('Waiting on Status', 'Decision Received') + ]; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.service.spec.ts new file mode 100644 index 0000000..2b25889 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.service.spec.ts @@ -0,0 +1,139 @@ +import { ActivatedRoute, Router } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ProcessingPageService } from './processing-page.service'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { DecisionState } from '../../../../shared/decision/decision-status'; +import { of } from 'rxjs'; +import { DecisionService } from '../../../../shared/decision/decision.service'; + +describe('Processing Page Service', () => { + + let service: ProcessingPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let decisionService: DecisionService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(ProcessingPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + decisionService = TestBed.get(DecisionService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should save and continue', () => { + spyOn(applicationWizardService, 'getNextRoute').and.returnValue('testRoute'); + spyOn(router, 'navigate').and.returnValue(true); + const activatedRoute = { + routeConfig: { + path: 'testPath' + } + }; + service.saveAndNavigate(activatedRoute); + expect(applicationWizardService.getNextRoute).toHaveBeenCalledWith('testPath'); + expect(router.navigate).toHaveBeenCalled(); + expect(() => { service.saveAndNavigate(null); }).toThrow(); + }); + + it('should review an application and mark complete', () => { + spyOn(service, 'markApplicationCompleted').and.returnValue(true); + service.reviewApplication().subscribe( + (result) => { + expect(result).toEqual(true); + } + ); + expect(service.markApplicationCompleted).toHaveBeenCalled(); + }); + + it('should submit application', () => { + spyOn(applicationWizardService, 'submitCreditApplication').and.returnValue(of(true)); + service.submitApplication().subscribe( + (result) => { + expect(result).toEqual(true); + } + ); + expect(applicationWizardService.submitCreditApplication).toHaveBeenCalled(); + }); + + it('should submit for decisioning', () => { + spyOn(applicationWizardService, 'submitApplicationForDecision').and.returnValue(of(true)); + service.submitForDecisioning().subscribe( + (result) => { + expect(result).toEqual(true); + } + ); + expect(applicationWizardService.submitApplicationForDecision).toHaveBeenCalled(); + }); + + it('should set as skipped decision', () => { + const existingDecision = service.getCreditApplication().decision; + spyOn(decisionService, 'createEmptyDecisionResult').and.callThrough(); + service.setAsSkippedDecision(); + expect(service.getCreditApplication().decision).not.toEqual(existingDecision); + expect(decisionService.createEmptyDecisionResult).toHaveBeenCalled(); + }); + + it('should request decision result from CRIF FAIL', () => { + const responses = [of(false), of(false), of(false), of(false), of(false), of(true)]; + spyOn(applicationWizardService, 'updateApplicationDecisionStatus').and.returnValue(of(false)); + let decisionFromCRIF = false; + service.requestDecisionResultFromCRIF().subscribe((result) => { + decisionFromCRIF = result; + + }); + expect(decisionFromCRIF).toEqual(false); + expect(applicationWizardService.updateApplicationDecisionStatus).toHaveBeenCalled(); + }); + + it('should request decision', () => { + spyOn(service, 'shouldRequestDecision').and.callThrough(); + const decision = service.shouldRequestDecision(); + expect(decision).toBeTruthy(); + expect(service.shouldRequestDecision).toHaveBeenCalled(); + }); + + it('should set as error decision', () => { + spyOn(service, 'setAsErrorDecision').and.callThrough(); + spyOn(decisionService, 'createErrorDecisionResult'); + service.setAsErrorDecision(DecisionState.ErrorSubmit); + expect(service.setAsErrorDecision).toHaveBeenCalled(); + expect(decisionService.createErrorDecisionResult).toHaveBeenCalled(); + }); + + it('should handle post application events', () => { + spyOn(service, 'handlePostApplicationEvents').and.callThrough(); + spyOn(applicationWizardService, 'handlePostApplicationEvents'); + service.handlePostApplicationEvents(); + expect(service.handlePostApplicationEvents).toHaveBeenCalled(); + expect(applicationWizardService.handlePostApplicationEvents).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.service.ts new file mode 100644 index 0000000..41fc321 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.service.ts @@ -0,0 +1,111 @@ +import { Injectable } from '@angular/core'; +import { interval, Observable, of, throwError } from 'rxjs'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { CreditApplication } from '../../../../shared/credit-application'; +import { ActivatedRoute, Router } from '@angular/router'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; +import { DecisionState } from '../../../../shared/decision/decision-status'; +import { delay, flatMap, map, mapTo, retryWhen } from 'rxjs/operators'; +import { DecisionService } from '../../../../shared/decision/decision.service'; +import { FormatService } from 'src/app/shared/pipes/format.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ProcessingPageService { + + private STANDARD_DELAY = 4000; + private STANDARD_RETRY = 29; // for a total of 30 + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private formatService: FormatService, + private decisionService: DecisionService, + private router: Router) { } + + public saveAndNavigate(route: ActivatedRoute): void { + if (!route) { + throw new Error('ProcessingPageService: saveAndNavigate called with null route'); + } + const nextRoute = this.applicationWizardService.getNextRoute(route.routeConfig.path); + this.router.navigate([nextRoute], {relativeTo: route}); + } + + public reviewApplication(): Observable { + this.markApplicationCompleted(); + const obs = of(null).pipe( + mapTo(true), + delay(this.STANDARD_DELAY) + ); + return obs; + } + + public submitApplication(): Observable { + return this.applicationWizardService.submitCreditApplication(this.getCreditApplication()); + } + + public submitForDecisioning(): Observable { + return this.applicationWizardService.submitApplicationForDecision(this.getCreditApplication()); + } + + public setAsSkippedDecision(): Observable { + const decision = this.decisionService.createEmptyDecisionResult(this.getCreditApplication().loanType.shouldDecision); + this.getCreditApplication().decision = decision; + const obs = of(null).pipe( + mapTo(true), + delay(this.STANDARD_DELAY) + ); + return obs; + } + + public getRetryStrategy(maxRetryCount: number = 29, delayInMils: number = 4000): any { + return (src: Observable) => src.pipe( + retryWhen(_ => { + return interval(delayInMils).pipe( + flatMap(count => count === maxRetryCount ? throwError('Reached maximum tries.') : of(count)) + ); + }) + ); + } + + public requestDecisionResultFromCRIF(): Observable { + return this.applicationWizardService.updateApplicationDecisionStatus().pipe( + map((val) => { + if (val === false) { + throw val; + } + return val; + }), + this.getRetryStrategy(this.STANDARD_RETRY, this.STANDARD_DELAY) + ); + } + + public getCreditApplication(): CreditApplication { + return this.creditApplicationService.getFullCreditApplication(); + } + + public shouldRequestDecision(): boolean { + return this.getCreditApplication().loanType.shouldDecision; + } + + public setAsErrorDecision(decisionState: DecisionState): void { + const decision = this.decisionService.createErrorDecisionResult(this.getCreditApplication().loanType.shouldDecision, decisionState); + this.getCreditApplication().decision = decision; + } + + public handlePostApplicationEvents(): void { + this.applicationWizardService.handlePostApplicationEvents(); + } + + public markApplicationCompleted(): any { + this.getCreditApplication().hasBeenCompleted = true; + } + + public getBorrowerFirstName(): string { + return this.formatService.formatTitleCase( + this.getCreditApplication().fullInfoBorrowers[0].personalInformation.firstName + ); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.spec.ts new file mode 100644 index 0000000..40c30bd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.spec.ts @@ -0,0 +1,51 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { ProcessingPage } from './processing-page'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +describe('Processing Page', () => { + + let page: ProcessingPage; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new ProcessingPage('PAGETITLES.CC.INTRO', 'decision', []); + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should be invalid if there is no credit application in state', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(null); + const isValid = page.isValidPage(applicationWizardService, creditApplicationService); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + expect(isValid.length).toBeGreaterThan(0); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.ts new file mode 100644 index 0000000..0c938fd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-page.ts @@ -0,0 +1,15 @@ +import { ApplicationPage } from '../../../application-page'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +export class ProcessingPage extends ApplicationPage { + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + if (applicationWizardService.getFullCreditApplication()) { + return []; + } else { + return ['ERROR']; + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-step.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-step.ts new file mode 100644 index 0000000..74f79cc --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/processing/processing-step.ts @@ -0,0 +1,33 @@ +export class ProcessingStep { + + public isComplete: boolean; + public isActive: boolean; + hasStatus: boolean; + preMessage: string; + postMessage: string; + + constructor(preMsg: string, postMsg: string, hasStatus = true) { + this.preMessage = preMsg; + this.postMessage = postMsg; + this.hasStatus = hasStatus; + } + + public setCurrent(isCurrent: boolean): void { + this.isActive = isCurrent; + } + + public setComplete(isComplete: boolean) { + this.isComplete = isComplete; + if (this.isComplete) { + this.isActive = false; + } + } + + public getMessage(): string { + return this.isComplete ? this.postMessage : this.preMessage; + } + + public setPostMessage(postMsg: string): void { + this.postMessage = postMsg; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.html new file mode 100644 index 0000000..7eeaf48 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.html @@ -0,0 +1,16 @@ +
+

HEADINGS.SPOUSE.{{localState.borrowerType}}

+
+ + +
+
+ + +
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.spec.ts new file mode 100644 index 0000000..1f27839 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.spec.ts @@ -0,0 +1,124 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { SpouseTypeInfoPageComponent } from './spouse-type-info-page.component'; +import { SpouseTypeInfoPageService } from './spouse-type-info-page.service'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { SpouseType } from '../../../../shared/credit-application'; +import { configureTestSuite } from 'ng-bullet'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; + +describe('SpouseInfoPageComponent', () => { + let component: SpouseTypeInfoPageComponent; + let fixture: ComponentFixture; + let service: SpouseTypeInfoPageService; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + SpouseTypeInfoPageComponent + ], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ], + schemas: [ + NO_ERRORS_SCHEMA + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SpouseTypeInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(SpouseTypeInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.spouseTypeGroup).toBeTruthy(); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + expect(component.commandPrevious).not.toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + fixture.detectChanges(); + spyOn(service, 'saveAndNavigate'); + component.localState.nextDisabled = false; + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + spyOn(service, 'cancelAndNavigate'); + component.commandPrevious(); + expect(service.cancelAndNavigate).toHaveBeenCalled(); + }); + + it('should bind the previous button to the correct command function', () => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonPrevious')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).not.toHaveBeenCalled(); + expect(component.commandPrevious).toHaveBeenCalled(); + }); + }); + + it('Should render existing spouse answer', () => { + const existingAnswer = {isFullInfoSpouse: CreditApplicationValues.CHOICE_YES}; + spyOn(service, 'getCurrentSpouseTypeValue').and.returnValue(existingAnswer); + fixture.detectChanges(); + expect(service.getCurrentSpouseTypeValue).toHaveBeenCalled(); + expect(component.pageForm.valid).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + expect(component.spouseTypeGroup.value).toEqual(existingAnswer); + }); + + it('should accurately submit all data on NEXT', () => { + const existingAnswer = {isFullInfoSpouse: CreditApplicationValues.CHOICE_YES}; + spyOn(service, 'getCurrentSpouseTypeValue').and.returnValue(existingAnswer); + fixture.detectChanges(); + expect(component.pageForm.valid).toBeTruthy(); + const buttonNext = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + expect(buttonNext.disabled).toBeFalsy(); + spyOn(service, 'saveAndNavigate').and.returnValue(true); + buttonNext.click(); + expect(service.saveAndNavigate).toHaveBeenCalledWith(existingAnswer, component.route, component.localState.borrowerType); + }); + + it('should patch previous data onInit', () => { + spyOn(service, 'getSpouseType').and.returnValue(SpouseType.FullInfoSpouse); + fixture.detectChanges(); + expect(component.spouseTypeGroup.value).toEqual({isFullInfoSpouse: CreditApplicationValues.CHOICE_YES}); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.ts new file mode 100644 index 0000000..dba3940 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { SpouseTypeInfoPageService } from './spouse-type-info-page.service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { SpouseType } from '../../../../shared/credit-application'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; + +@Component({ + selector: 'lw-spouse-type-info-page', + templateUrl: './spouse-type-info-page.component.html' +}) +export class SpouseTypeInfoPageComponent implements OnInit { + + public pageForm: FormGroup; + public spouseTypeGroup: FormGroup; + private readonly FORM_STATE_INVALID = 'INVALID'; + private subscription: Subscription; + + public savingData = null; + + public localState = { + nextDisabled: true, + previousDisabled: false, + nextTopic: '', + borrowerType: BorrowerType.PRIMARY_BORROWER, + savingStep: null + }; + + constructor( + private spouseTypeInfoPageService: SpouseTypeInfoPageService, + public route: ActivatedRoute) { } + + public ngOnInit() { + this.initializeFormGroups(); + this.updateLocalState(this.pageForm.value); + } + + public initializeFormGroups() { + this.pageForm = this.spouseTypeInfoPageService.createPageGroup(); + this.spouseTypeGroup = this.pageForm.get(SpouseTypeInfoPageService.SPOUSE_TYPE_FORM_NAME); + this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + this.spouseTypeGroup.patchValue(this.spouseTypeInfoPageService.getCurrentSpouseTypeValue(this.route)); + } + + public updateLocalState(modelValue: any): void { + if (this.subscription) { + this.subscription.unsubscribe(); + } + this.localState.borrowerType = this.spouseTypeInfoPageService.determineBorrowerType(this.route); + this.localState.nextTopic = this.spouseTypeInfoPageService.getNextTopicMessage(this.localState.borrowerType); + this.subscription = this.pageForm.valueChanges.subscribe(() => { + this.updateLocalState(this.pageForm.value); + }); + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + const isFullInfoSpouse = this.pageForm.value[SpouseTypeInfoPageService.SPOUSE_TYPE_FORM_NAME]; + this.spouseTypeInfoPageService.saveAndNavigate(isFullInfoSpouse, this.route, this.localState.borrowerType); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + } + + public commandPrevious() { + this.spouseTypeInfoPageService.cancelAndNavigate(this.route); + } + + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.service.spec.ts new file mode 100644 index 0000000..0927162 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.service.spec.ts @@ -0,0 +1,113 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { SpouseTypeInfoPageService } from './spouse-type-info-page.service'; +import { IsFullInfoSpouse } from '../../../field-groups/spouse-type/is-full-info-spouse-information'; +import { BorrowerType } from '../../../../shared/borrower-type'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +describe('Spouse Type Info Page Service', () => { + + let service: SpouseTypeInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let creditApplicationService: CreditApplicationService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [ + NO_ERRORS_SCHEMA + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(SpouseTypeInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + creditApplicationService = TestBed.get(CreditApplicationService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create the top level form group', () => { + spyOn(service, 'createSpouseTypeInformationGroup').and.callThrough(); + const pageGroup = service.createPageGroup(); + const isSameGroup = pageGroup.get(SpouseTypeInfoPageService.SPOUSE_TYPE_FORM_NAME); + expect(service.createSpouseTypeInformationGroup).toHaveBeenCalled(); + CommonGroups.IS_FULL_INFO_SPOUSE.fields.forEach((eachField) => { + expect(isSameGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should create an address info group', () => { + const addressGroup = service.createSpouseTypeInformationGroup(); + expect(addressGroup).toBeTruthy(); + CommonGroups.IS_FULL_INFO_SPOUSE.fields.forEach((eachField) => { + expect(addressGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should provide the spouse type', () => { + spyOn(applicationWizardService, 'getSpouseType').and.callThrough(); + service.getSpouseType(BorrowerType.PRIMARY_BORROWER); + expect(applicationWizardService.getSpouseType).toHaveBeenCalledWith(BorrowerType.PRIMARY_BORROWER); + }); + + it('should navigate the application, on successful save', () => { + spyOn(creditApplicationService, 'saveSpouseTypeInformation').and.returnValue(of(true)); + spyOn(router, 'navigate'); + const info = {}; + info.isFullInfoSpouse = CreditApplicationValues.CHOICE_YES; + const borrower = BorrowerType.PRIMARY_BORROWER_SPOUSE; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute, borrower); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'saveSpouseTypeInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + const info = {}; + info.isFullInfoSpouse = CreditApplicationValues.CHOICE_YES; + const borrower = BorrowerType.PRIMARY_BORROWER_SPOUSE; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute, borrower); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('cancelling should always navigate', () => { + spyOn(router, 'navigate'); + const activatedRoute = {}; + service.cancelAndNavigate(activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.service.ts new file mode 100644 index 0000000..3a3bcb1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { PageService } from '../../page-service'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { TattleSpousePersonalInformation } from '../../../field-groups/tattle-spouse/tattle-spouse-personal-information'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { Individual, SpouseType } from '../../../../shared/credit-application'; +import { IsFullInfoSpouse } from '../../../field-groups/spouse-type/is-full-info-spouse-information'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +@Injectable({ + providedIn: 'root' +}) +export class SpouseTypeInfoPageService implements PageService { + + public static readonly SPOUSE_TYPE_FORM_NAME = 'spouseTypeGroup'; + private CURRENT_ROUTE = 'applicant-spouse-type'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private router: Router, + private formBuilder: FormBuilder) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + + const spouseTypeGroup = this.createSpouseTypeInformationGroup(); + group.addControl(SpouseTypeInfoPageService.SPOUSE_TYPE_FORM_NAME, spouseTypeGroup); + + return group; + } + + public createSpouseTypeInformationGroup(): FormGroup { + const memberGroup = this.formBuilder.group({}); + + CommonGroups.IS_FULL_INFO_SPOUSE.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; + } + + public getSpouseType(individual: BorrowerType): SpouseType { + return this.applicationWizardService.getSpouseType(individual); + } + + public saveAndNavigate(spouseTypeInformation: IsFullInfoSpouse, route: ActivatedRoute, individual: BorrowerType) { + this.creditApplicationService.saveSpouseTypeInformation(spouseTypeInformation, individual).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public determineCurrentRoute(individual: BorrowerType): string { + switch (individual) { + case BorrowerType.PRIMARY_BORROWER: + return 'applicant-spouse-type'; + case BorrowerType.CO_BORROWER: + return 'co-applicant-spouse-type'; + } + } + + public cancelAndNavigate(route: ActivatedRoute) { + const nextRoute = this.applicationWizardService.getPreviousRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + + public getNextTopicMessage(individual: BorrowerType): string { + this.CURRENT_ROUTE = this.determineCurrentRoute(individual); + return this.applicationWizardService.getNextTopicMessage(this.CURRENT_ROUTE); + } + + public determineBorrowerType(route: ActivatedRoute): BorrowerType { + if (route.snapshot.data[0]) { + return route.snapshot.data[0].individual; + } + return BorrowerType.PRIMARY_BORROWER; + } + + public getCurrentSpouseTypeValue(route: ActivatedRoute): any { + const borrowerType = this.determineBorrowerType(route); + const spouseType = this.getSpouseType(borrowerType); + if (spouseType === SpouseType.FullInfoSpouse) { + return {isFullInfoSpouse: CreditApplicationValues.CHOICE_YES}; + } else if (spouseType === SpouseType.TattleSpouse) { + return {isFullInfoSpouse: CreditApplicationValues.CHOICE_NO}; + } + return { isFullInfoSpouse: null }; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.ts new file mode 100644 index 0000000..a66ea95 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/spouse-type/spouse-type-info-page.ts @@ -0,0 +1,23 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { ApplicationPage } from '../../../application-page'; +import { BorrowerType } from '../../../../shared/borrower-type'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +export class SpouseTypeInfoPage extends ApplicationPage { + + private INDIVIDUAL_REF: string; + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + if (this.route === 'applicant-spouse-type') { + return applicationWizardService.isApplicantNonSpouse(BorrowerType.PRIMARY_BORROWER); + } else if (this.route === 'co-applicant-spouse-type') { + return applicationWizardService.isApplicantNonSpouse(BorrowerType.CO_BORROWER); + } + return false; + } + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + // @TODO: #565 removes this page + return []; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.html new file mode 100644 index 0000000..696cad9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.html @@ -0,0 +1,20 @@ +
+
+
SUMMARY.GENERAL.{{title}} + +
+
+
SUMMARY.GENERAL.{{loanCategory}}
+
{{limitRequested | currency}}
+
SUMMARY.GENERAL.PURPOSE
+
{{purposeOfLoanSignature}}
+
SUMMARY.GENERAL.PURPOSE
+
{{purposeOfLoan}}
+
SUMMARY.GENERAL.LOAN_TERM
+
{{loanTerms}} Months
+
SUMMARY.GENERAL.HELOC_RATE_MOD
+
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.spec.ts new file mode 100644 index 0000000..7d42b02 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.spec.ts @@ -0,0 +1,70 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { SummaryPageService } from './summary-page.service'; +import { ApplicationSummaryComponent } from './application-summary.component'; +import { CreditApplication } from '../../../../shared/credit-application'; +import { apiTranslatorPreTranslate } from 'src/app/testing/shared-mocks/api-translator-pre-post'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +describe('ApplicationSummaryComponent', () => { + let component: ApplicationSummaryComponent; + let fixture: ComponentFixture; + let creditApplication: CreditApplication; + let service: SummaryPageService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ApplicationSummaryComponent + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ApplicationSummaryComponent); + component = fixture.componentInstance; + service = TestBed.get(SummaryPageService); + creditApplication = apiTranslatorPreTranslate; + + component.creditApplication = creditApplication; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit an event when the edit button is clicked', () => { + fixture.detectChanges(); + let eventHappened = false; + component.requestToEdit.subscribe((event) => { + eventHappened = true; + }); + const button = fixture.debugElement.query(By.css('button')); + expect(button).toBeTruthy(); + button.nativeElement.click(); + fixture.detectChanges(); + expect(eventHappened).toEqual(true); + }); + + it('should display loan term when termOfLoan exists', () => { + expect(component.hasLoanTerms).toEqual(false); + fixture.detectChanges(); + expect(component.hasLoanTerms).toEqual(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.ts new file mode 100644 index 0000000..4e50072 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/application-summary.component.ts @@ -0,0 +1,71 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {CreditApplication} from '../../../../shared/credit-application'; +import {LoanCategory} from 'src/app/shared/loan-type/loan-category'; +import {LoanTitle} from '../../../../shared/loan-type/loan-title'; +import { SelectOptions } from 'src/app/shared/options/select-options'; +import { LoanPurpose } from 'src/app/shared/loan-type/loan-purpose'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; + +@Component({ + selector: 'lw-application-summary', + templateUrl: './application-summary.component.html', + styleUrls: ['./summary-page.component.scss'] +}) +export class ApplicationSummaryComponent implements OnInit { + + public title: string; + public limitRequested: string; + public purposeOfLoanSignature: string; + public purposeOfLoan: string; + public loanCategory: string; + public loanTerms: string; + public hasLoanTerms: Boolean = false; + public isHelocRateMod: Boolean = false; + public askPurposeOfLoanSignature: Boolean = false; + public askPurposeOfLoan: Boolean = false; + + @Input() + public creditApplication: CreditApplication; + + @Output() + public requestToEdit: EventEmitter; + + constructor() { + this.requestToEdit = new EventEmitter(); + } + + ngOnInit(): void { + this.loanCategory = this.creditApplication.loanType.category === LoanCategory.CreditCard ? '' : 'LOAN_LIMIT'; + this.title = this.creditApplication.loanType.category === LoanCategory.CreditCard ? '' : 'LOAN_TITLE'; + this.isHelocRateMod = this.creditApplication.loanType.title === LoanTitle.HELOC_RateMod; + this.askPurposeOfLoanSignature = this.creditApplication.loanType.category === LoanCategory.Signature; + this.askPurposeOfLoan = this.creditApplication.loanType.category === LoanCategory.Equity; + this.purposeOfLoanSignature = this.creditApplication.purposeOfLoanSignature; + + if (this.creditApplication.purposeOfLoan) { + if (this.askPurposeOfLoan) { + this.purposeOfLoan = this.creditApplication.purposeOfLoan.replace(/_/g, ' '); + } else { + this.purposeOfLoan = this.creditApplication.purposeOfLoan; + } + } + + if (this.creditApplication && this.creditApplication.limitRequested) { + this.limitRequested = this.creditApplication.limitRequested.toString(); + if (this.creditApplication.termOfLoan) { + this.loanTerms = this.creditApplication.termOfLoan.toString(); + this.hasLoanTerms = true; + } + } else { + this.limitRequested = '0'; + } + } + + public commandRequestEdit() { + this.requestToEdit.emit('edit'); // @TODO This may be an id later. + } + + public isCreditCard(){ + return this.loanCategory == LoanCategory.CreditCard; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.html new file mode 100644 index 0000000..3cd95e9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.html @@ -0,0 +1,55 @@ + +
+
+
{{title}} + +
+
+
SUMMARY.COLLATERAL.ADDRESS_SAME
+
{{collateralInformation.isAddressSame | booleanString}}
+
SUMMARY.COLLATERAL.ESTIMATED_VALUE
+
{{collateralInformation.estimatedValue | currency}}
+
Address
+
{{collateralInformation.addressLine1}} +
+ {{collateralInformation.addressLine2}} +
+
+ {{collateralInformation.city}}, {{collateralInformation.state}} {{collateralInformation.zipcode}} +
+
SUMMARY.COLLATERAL.OCCUPANCY_TYPE
+
{{collateralInformation.occupancyType | occupancyType}}
+
SUMMARY.COLLATERAL.PROPERTY_TYPE
+
{{collateralInformation.propertyType | propertyType}}
+
SUMMARY.COLLATERAL.PROPERTY_TYPE
+
{{collateralInformation.propertyType | mfgHomePropertyType}}
+ +
{{ collateralInformation.files[0].description }} document(s):
+
{{collateralInformation.files | uploadedFiles}}
+ +
SUMMARY.COLLATERAL.FIRST_MORTGAGE
+
{{collateralInformation.hasFirstMortgage | booleanString}}
+
{{ collateralInformation.firstMortgageBalanceFiles[0].description }} document(s):
+
{{collateralInformation.firstMortgageBalanceFiles | uploadedFiles}}
+ +
SUMMARY.COLLATERAL.PAYOFF_FIRST_MORTGAGE
+
{{collateralInformation.payoffFirstMortgage ? 'Yes' : 'No'}}
+
SUMMARY.COLLATERAL.SECOND_MORTGAGE
+
{{collateralInformation.hasSecondMortgage | booleanString}}
+
SUMMARY.COLLATERAL.PAYOFF_SECOND_MORTGAGE
+
{{collateralInformation.payoffSecondMortgage ? 'Yes' : 'No'}}
+
SUMMARY.COLLATERAL.IS_BEING_IMPROVED
+
{{collateralInformation.isBeingImproved | booleanString}}
+
SUMMARY.COLLATERAL.IS_LISTED
+
{{collateralInformation.isListedForSale | booleanString}}
+
SUMMARY.COLLATERAL.ON_RENTED_LOT
+
{{collateralInformation.onRentedLot | booleanString}}
+
SUMMARY.COLLATERAL.RENT_PAID
+
{{collateralInformation.rentPaid}}
+
SUMMARY.COLLATERAL.ANNUAL_PROPERTY_TAX
+
{{collateralInformation.annualPropertyTax | currency}}
+
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.spec.ts new file mode 100644 index 0000000..455aa6b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.spec.ts @@ -0,0 +1,84 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { CollateralSummaryComponent } from './collateral-summary.component'; +import { OccupancyTypePipe } from '../../../../shared/pipes/occupancy-type.pipe'; +import { BooleanStringPipe } from '../../../../shared/pipes/boolean-string.pipe'; +import { UploadedFilesPipe } from 'src/app/shared/pipes/uploaded-files.pipe'; +import { PropertyTypePipe, MfgHomePropertyTypePipe } from '../../../../shared/pipes/property-type.pipe'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { configureTestSuite } from 'ng-bullet'; + +describe('CollateralSummaryComponent', () => { + let component: CollateralSummaryComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + CollateralSummaryComponent, + OccupancyTypePipe, + BooleanStringPipe, + PropertyTypePipe, + MfgHomePropertyTypePipe, + UploadedFilesPipe + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [ NO_ERRORS_SCHEMA ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CollateralSummaryComponent); + component = fixture.componentInstance; + component.collateralInformation = { + estimatedValue: 5446, + propertyType: 'Vacant', + hasFirstMortgage: CreditApplicationValues.CHOICE_YES, + hasSecondMortgage: CreditApplicationValues.CHOICE_YES, + payoffFirstMortgage: false, + payoffSecondMortgage: false, + isBeingImproved: CreditApplicationValues.CHOICE_YES, + isListedForSale: CreditApplicationValues.CHOICE_NO, + occupancyType: 'Owner Occupied', + isAddressSame: CreditApplicationValues.CHOICE_YES, + addressLine1: '', + city: '', + state: '', + zipcode: '', + files: [], + firstMortgageBalanceFiles: [], + isUploadingFirstMortgageBalanceFiles: false, + isUploading: false + }; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit an event when the edit button is clicked', () => { + fixture.detectChanges(); + let eventHappened = false; + component.requestToEdit.subscribe((event) => { + eventHappened = true; + }); + const button = fixture.debugElement.query(By.css('button')).nativeElement; + button.click(); + fixture.detectChanges(); + expect(eventHappened).toEqual(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.ts new file mode 100644 index 0000000..fe56264 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/collateral-summary.component.ts @@ -0,0 +1,40 @@ +import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; +import { CollateralInformationGroup } from '../../../../shared/credit-application'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; + +@Component({ + selector: 'lw-collateral-summary', + templateUrl: './collateral-summary.component.html', + styleUrls: ['./summary-page.component.scss'] +}) +export class CollateralSummaryComponent implements OnInit { + + public isMfgHome: boolean; + + @Input() + public title: string; + + @Input() + public collateralInformation: CollateralInformationGroup; + + @Input() + public loanCode: LoanCode; + + @Input() + public isHelocRateMod: boolean; + + @Output() + public requestToEdit: EventEmitter; + + constructor() { + this.requestToEdit = new EventEmitter(); + } + + public ngOnInit() { + this.isMfgHome = this.loanCode === LoanCode.MANUFACTURED_HOME_LOAN; + } + + public commandRequestEdit() { + this.requestToEdit.emit('edit'); // @TODO This may be an id later. + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.html new file mode 100644 index 0000000..eeed60e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.html @@ -0,0 +1,64 @@ +
+
+
{{title}} + +
+
+
SUMMARY.EMPLOYER.CURRENT
+
SUMMARY.EMPLOYER.OTHER #{{i}}
+
SUMMARY.EMPLOYER.EMPLOYERNAME
+
{{employer.employerName}}
+
SUMMARY.EMPLOYER.EMPLOYERTYPE
+
{{employer.employmentType | employmentType}}: {{employer.jobTitle}}
+
SUMMARY.EMPLOYER.SALARY
+
{{employer.salary | currency}}
+
Proof of Income document(s)
+
{{employer.files | uploadedFiles}}
+
SUMMARY.EMPLOYER.SCHEDULE
+
{{employer.schedule}} + + ({{employer.weeklyHours}} weekly hours) + +
+
SUMMARY.EMPLOYER.LENGTH
+
{{employer.employmentLengthYears}} year(s), {{employer.employmentLengthMonths}} + month(s) +
+
SUMMARY.EMPLOYER.WORKPHONE
+
{{employer.workPhoneNumber}}
+


+
+ +
+
SUMMARY.EMPLOYER.OTHERINCOME
+
{{otherIncomeInformation[0].amount | currency}}
+
SUMMARY.EMPLOYER.SOURCE
+
{{otherIncomeInformation[0].incomeName}}
+
Proof of Income document(s)
+
{{otherIncomeInformation[0].files | uploadedFiles}}
+


+
SUMMARY.EMPLOYER.OTHERINCOME
+
{{otherIncomeInformation[1].amount | currency}}
+
SUMMARY.EMPLOYER.SOURCE
+
{{otherIncomeInformation[1].incomeName}}
+
Proof of Income document(s)
+
{{otherIncomeInformation[1].files | uploadedFiles}}
+


+
SUMMARY.EMPLOYER.OTHERINCOME
+
{{otherIncomeInformation[2].amount | currency}}
+
SUMMARY.EMPLOYER.SOURCE
+
{{otherIncomeInformation[2].incomeName}}
+
Proof of Income document(s)
+
{{otherIncomeInformation[2].files | uploadedFiles}}
+
+ +


+ +
+
SUMMARY.EMPLOYER.CHILDSUPPORT
+
{{alimonyInformation.childSupportAmount}}
+
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.spec.ts new file mode 100644 index 0000000..4f50875 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.spec.ts @@ -0,0 +1,92 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { IncomeSummaryComponent } from './income-summary.component'; +import { EmploymentTypePipe } from '../../../../shared/pipes/employment-type.pipe'; +import { PhoneNumberPipe } from '../../../../shared/pipes/phone-number.pipe'; +import { configureTestSuite } from 'ng-bullet'; +import { UploadedFilesPipe } from 'src/app/shared/pipes/uploaded-files.pipe'; + +describe('IncomeSummaryComponent', () => { + let component: IncomeSummaryComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + IncomeSummaryComponent, + EmploymentTypePipe, + PhoneNumberPipe, + UploadedFilesPipe + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(IncomeSummaryComponent); + component = fixture.componentInstance; + component.employmentInformation = [{ + 'employerName': 'NVISIA LLC', + 'employmentType': 'Full time', + 'jobTitle': 'Senior Technical Architect', + 'salary': 23434, + 'schedule': 'Annually', + 'employmentLengthYears': 2, + 'employmentLengthMonths': 2, + 'workPhoneNumber': '1112223333' + }, + { + 'employerName': 'LYFT', + 'employmentType': 'Part time', + 'jobTitle': 'Driver', + 'salary': 15, + 'schedule': 'Hourly', + 'weeklyHours': 12, + 'employmentLengthYears': 4, + 'employmentLengthMonths': 2, + 'workPhoneNumber': '2622225555' + }, + { + 'employerName': 'UBER', + 'employmentType': 'Part time', + 'jobTitle': 'Driver', + 'salary': 20, + 'schedule': 'Hourly', + 'weeklyHours': 10, + 'employmentLengthYears': 5, + 'employmentLengthMonths': 2, + 'workPhoneNumber': '2625554433' + }]; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit an event when the edit button is clicked', () => { + fixture.detectChanges(); + let eventHappened = false; + component.requestToEdit.subscribe((event) => { + eventHappened = true; + }); + const button = fixture.debugElement.query(By.css('button')).nativeElement; + button.click(); + fixture.detectChanges(); + expect(eventHappened).toEqual(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.ts new file mode 100644 index 0000000..e334fc7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/income-summary.component.ts @@ -0,0 +1,35 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { EmploymentInformation } from '../../../field-groups/employment/employment-information'; +import { IncomeInformation } from '../../../field-groups/employment/income-information'; +import { AlimonyInformation } from '../../../field-groups/employment/alimony-information'; + +@Component({ + selector: 'lw-income-summary', + templateUrl: './income-summary.component.html', + styleUrls: ['./summary-page.component.scss'] +}) +export class IncomeSummaryComponent { + + @Input() + public title: string; + + @Input() + public employmentInformation: Array; + + @Input() + public otherIncomeInformation: Array; + + @Input() + public alimonyInformation: AlimonyInformation; + + @Output() + public requestToEdit: EventEmitter; + + constructor() { + this.requestToEdit = new EventEmitter(); + } + + public commandRequestEdit() { + this.requestToEdit.emit('edit'); // @TODO This may be an id later. + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.html new file mode 100644 index 0000000..95d30f1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.html @@ -0,0 +1,30 @@ + +
+
+
{{title}} + +
+
+
SUMMARY.PERSONAL.FULL_NAME
+
{{personalInformation.firstName}} {{personalInformation.middleInitial}} {{personalInformation.lastName}}
+
SUMMARY.PERSONAL.DOB
+
{{personalInformation.dateOfBirth}}
+
SUMMARY.PERSONAL.SSN
+
{{personalInformation.socialSecurityNumber | ssn}}
+
Proof of being a student document(s)
+
{{personalInformation.files | uploadedFiles}}
+
SUMMARY.PERSONAL.PRIMARY_PHONE
+
{{personalInformation.primaryPhoneNumber}} ({{personalInformation.primaryPhoneNumberType | phoneType}})
+
SUMMARY.PERSONAL.SECONDARY_PHONE
+
{{personalInformation.secondaryPhoneNumber}} ({{personalInformation.secondaryPhoneNumberType | phoneType}})
+
SUMMARY.PERSONAL.EMAIL
+
{{personalInformation.email}}
+
SUMMARY.PERSONAL.PRIMARY_APPLICANT_EMAIL
+
{{personalInformation.primaryApplicantEmail}}
+
SUMMARY.PERSONAL.CO_APPLICANT_EMAIL
+
{{personalInformation.coApplicantEmail}}
+
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.spec.ts new file mode 100644 index 0000000..3841240 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.spec.ts @@ -0,0 +1,73 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { PersonalSummaryComponent } from './personal-summary.component'; +import { SsnPipe } from '../../../../shared/pipes/ssn.pipe'; +import { PhoneTypePipe } from '../../../../shared/pipes/phone-type.pipe'; +import { PhoneNumberPipe } from '../../../../shared/pipes/phone-number.pipe'; +import { configureTestSuite } from 'ng-bullet'; +import { PhoneTypeEnum } from 'src/app/shared/api-models/api-enums'; +import { UploadedFilesPipe } from 'src/app/shared/pipes/uploaded-files.pipe'; + +describe('PersonalSummaryComponent', () => { + let component: PersonalSummaryComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + PersonalSummaryComponent, + SsnPipe, + PhoneTypePipe, + PhoneNumberPipe, + UploadedFilesPipe + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PersonalSummaryComponent); + component = fixture.componentInstance; + component.personalInformation = { + 'firstName': 'John', + 'middleInitial': 'J', + 'lastName': 'Doe', + 'dateOfBirth': '12021980', + 'socialSecurityNumber': '333-44-5555', + 'primaryPhoneNumberType': PhoneTypeEnum.Mobile, + 'primaryPhoneNumber': '(414) 732-1111', + 'secondaryPhoneNumberType': null, + 'secondaryPhoneNumber': null, + 'email': 'john.do@gmail.com' + }; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit an event when the edit button is clicked', () => { + fixture.detectChanges(); + let eventHappened = false; + component.requestToEdit.subscribe((event) => { + eventHappened = true; + }); + const button = fixture.debugElement.query(By.css('button')).nativeElement; + button.click(); + fixture.detectChanges(); + expect(eventHappened).toEqual(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.ts new file mode 100644 index 0000000..8bd0165 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/personal-summary.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, Output, EventEmitter, Pipe, PipeTransform } from '@angular/core'; +import { PersonalInformation } from '../../../field-groups/personal/personal-information'; + +@Component({ + selector: 'lw-personal-summary', + templateUrl: './personal-summary.component.html', + styleUrls: ['./summary-page.component.scss'] +}) +export class PersonalSummaryComponent { + + @Input() + public title: string; + + @Input() + public personalInformation: PersonalInformation; + + @Input() + public isHelocRateMod: boolean; + + @Output() + public requestToEdit: EventEmitter; + + constructor() { + this.requestToEdit = new EventEmitter(); + } + + public commandRequestEdit() { + this.requestToEdit.emit('edit'); // @TODO This may be an id later. + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.html new file mode 100644 index 0000000..ba1e322 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.html @@ -0,0 +1,54 @@ +
+
+
{{title}} + +
+
{{title}}
+ + + + +
+
SUMMARY.ADDRESS.ADDRESS_SAME
+
BUTTONS.YES
+
+
+ +
+
SUMMARY.ADDRESS.STREET_ADDRESS
+
{{addressInformation.addressLine1}} +
+ {{addressInformation.addressLine2}} +
+
+ {{addressInformation.city}}, {{addressInformation.state}} {{addressInformation.zipcode}} +
+
+
+
SUMMARY.ADDRESS.TENANT_STATUS
+
{{residenceInformation.residenceStatus | addressStatus}}
+
SUMMARY.ADDRESS.MONTHLY_PAYMENT
+
{{residenceInformation.residenceMonthlyPayment | currency}}
+
SUMMARY.ADDRESS.LENGTH
+
{{residenceInformation.residenceTimeYears}} year(s), {{residenceInformation.residenceTimeMonths}} + month(s) +
+
+ +
+
SUMMARY.ADDRESS.MAILING_ADDRESS
+
{{mailAddressInformation.addressLine1}} +
+ {{mailAddressInformation.addressLine2}} +
+
+ {{mailAddressInformation.city}}, {{mailAddressInformation.state}} {{mailAddressInformation.zipcode}} +
+
+
+ + +
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.spec.ts new file mode 100644 index 0000000..d6e2dfd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.spec.ts @@ -0,0 +1,75 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { ResidenceSummaryComponent } from './residence-summary.component'; +import { AddressStatusPipe } from '../../../../shared/pipes/address-status.pipe'; +import { configureTestSuite } from 'ng-bullet'; + +describe('ResidenceSummaryComponent', () => { + let component: ResidenceSummaryComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + ResidenceSummaryComponent, + AddressStatusPipe + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ResidenceSummaryComponent); + component = fixture.componentInstance; + component.addressInformation = { + 'addressLine1': '111 E Franklin St', + 'addressLine2': '', + 'zipcode': '53207', + 'city': 'Milwaukee', + 'state': 'WI' + }; + component.residenceInformation = { + 'residenceStatus': '1', + 'residenceMonthlyPayment': 2343, + 'residenceTimeYears': '3', + 'residenceTimeMonths': '6', + 'isMailingAddressSame': true, + }; + component.mailAddressInformation = { + 'addressLine1': '', + 'addressLine2': '', + 'zipcode': '', + 'city': '', + 'state': '' + }; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit an event when the edit button is clicked', () => { + fixture.detectChanges(); + let eventHappened = false; + component.requestToEdit.subscribe((event) => { + eventHappened = true; + }); + const button = fixture.debugElement.query(By.css('button')).nativeElement; + button.click(); + fixture.detectChanges(); + expect(eventHappened).toEqual(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.ts new file mode 100644 index 0000000..64dd4c6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/residence-summary.component.ts @@ -0,0 +1,49 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { AddressInformation } from '../../../field-groups/address/address-information'; +import {ResidenceInformation} from '../../../field-groups/address/residence-information'; + +@Component({ + selector: 'lw-residence-summary', + templateUrl: './residence-summary.component.html', + styleUrls: ['./summary-page.component.scss'] +}) +export class ResidenceSummaryComponent { + + @Input() + public title: string; + + @Input() + public addressInformation: AddressInformation; + + @Input() + public priorAddress: boolean; + + @Input() + public mailAddressInformation: AddressInformation; + + @Input() + public residenceInformation: ResidenceInformation; + + @Input() + public priorAddressInformation: AddressInformation; + + @Input() + public priorResidenceInformation: ResidenceInformation; + + @Input() + public hasSameAddressAsSpouse: boolean; + + @Input() + public isHelocRateMod: boolean; + + @Output() + public requestToEdit: EventEmitter; + + constructor() { + this.requestToEdit = new EventEmitter(); + } + + public commandRequestEdit() { + this.requestToEdit.emit('edit'); // @TODO This may be an id later. + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-info-page.spec.ts new file mode 100644 index 0000000..0f0162d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-info-page.spec.ts @@ -0,0 +1,50 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed, async } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { SummaryInfoPage } from './summary-info-page'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +describe('Summary Info Page', () => { + let page: SummaryInfoPage; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new SummaryInfoPage('PAGETITLES.COMMON.SUMMARY', 'cc-summary', []), + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should be invalid if there is no credit application in state', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(null); + const isValid = page.isValidPage(applicationWizardService, creditApplicationService); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + expect (isValid.length).toBeGreaterThan(0); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-info-page.ts new file mode 100644 index 0000000..a2b540f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-info-page.ts @@ -0,0 +1,15 @@ +import { ApplicationPage } from '../../../application-page'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +export class SummaryInfoPage extends ApplicationPage { + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + if (applicationWizardService.getFullCreditApplication()) { + return []; + } else { + return ['ERROR']; + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.html new file mode 100644 index 0000000..5006a1e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.html @@ -0,0 +1,197 @@ +

SUMMARY.PRIMARY_HEADING

+
+
COPY.SUMMARY
+ + + + + + + + + + + + + + + + + + + +

SUMMARY.PRIMARY_SPOUSE_HEADING

+
+ + + + +
+ + +

SUMMARY.PRIMARY_SPOUSE_HEADING

+
+ + + + + + +
+ + +

SUMMARY.CO_APP_HEADING

+
+ + + + +
+ + +
+
+ + +
+ + +

SUMMARY.CO_APP_SPOUSE_HEADING

+
+ + + + +
+ + +

SUMMARY.CO_APP_SPOUSE_HEADING

+
+ + + + + + +
+ + +

SUMMARY.ADDITIONAL_INFO

+
+
+ + +
+
+
+ +

SUMMARY.NOTE_HEADING

+
+
+ +
+ +
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.scss new file mode 100644 index 0000000..44d8df5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.scss @@ -0,0 +1,3 @@ +.summary-card-field-title { + font-weight: bold; +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.spec.ts new file mode 100644 index 0000000..3b77a1d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.spec.ts @@ -0,0 +1,120 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { SummaryPageComponent } from './summary-page.component'; +import { SummaryPageService } from './summary-page.service'; +import { ApplicationSummaryComponent } from './application-summary.component'; +import { PersonalSummaryComponent } from './personal-summary.component'; +import { ResidenceSummaryComponent } from './residence-summary.component'; +import { SsnPipe } from '../../../../shared/pipes/ssn.pipe'; +import { EmploymentTypePipe } from '../../../../shared/pipes/employment-type.pipe'; +import { AddressStatusPipe } from '../../../../shared/pipes/address-status.pipe'; +import { PhoneNumberPipe } from '../../../../shared/pipes/phone-number.pipe'; +import { PhoneTypePipe } from '../../../../shared/pipes/phone-type.pipe'; +import { CollateralSummaryComponent } from './collateral-summary.component'; +import { BooleanStringPipe } from '../../../../shared/pipes/boolean-string.pipe'; +import { OccupancyTypePipe } from '../../../../shared/pipes/occupancy-type.pipe'; +import { PropertyTypePipe, MfgHomePropertyTypePipe } from '../../../../shared/pipes/property-type.pipe'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientModule } from '@angular/common/http'; +import { NoteFormComponent } from '../../../field-groups/summary/note-form.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { UploadedFilesPipe } from 'src/app/shared/pipes/uploaded-files.pipe'; + +describe('SummaryPageComponent', () => { + let component: SummaryPageComponent; + let fixture: ComponentFixture; + let service: SummaryPageService; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientModule, + TranslateModule.forRoot() + ], + declarations: [ + ApplicationSummaryComponent, + PersonalSummaryComponent, + CollateralSummaryComponent, + SummaryPageComponent, + SsnPipe, + EmploymentTypePipe, + AddressStatusPipe, + PhoneNumberPipe, + PhoneTypePipe, + BooleanStringPipe, + OccupancyTypePipe, + PropertyTypePipe, + MfgHomePropertyTypePipe, + UploadedFilesPipe, + NoteFormComponent + ], + providers: [], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SummaryPageComponent); + component = fixture.componentInstance; + service = TestBed.get(SummaryPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should show all additional residences', async () => { + fixture.detectChanges(); + const preChangeResidence = fixture.debugElement.queryAll(By.css('lw-residence-summary')); + expect(preChangeResidence).toBeTruthy(); + expect(preChangeResidence.length).toEqual(1); + expect(component.creditApplication.fullInfoBorrowers[0].priorAddressInformation).toBeFalsy(); + const newApplication = Object.assign({}, component.creditApplication); + newApplication.fullInfoBorrowers[0].priorAddressInformation = [{ + 'addressLine1': '526 E Lincoln Ave', + 'addressLine2': '', + 'zipcode': '53207', + 'city': 'Milwaukee', + 'state': 'WI' + }]; + newApplication.fullInfoBorrowers[0].priorResidenceInformation = [{ + 'isMailingAddressSame': true, + 'residenceStatus': '1', + 'residenceMonthlyPayment': 2343, + 'residenceTimeYears': '1', + 'residenceTimeMonths': '6', + }]; + component.creditApplication = newApplication; + fixture.detectChanges(); + // Logged bug #919. When that is resolved this test should pass. + // const postChangeResidence = fixture.debugElement.queryAll(By.css('lw-residence-summary')); + // expect(postChangeResidence.length).toEqual(2); + expect(component.creditApplication.fullInfoBorrowers[0].priorAddressInformation.length).toEqual(1); + }); + + it('should not allow user to submit if the note has invalid symbols/text', () => { + const input = fixture.debugElement.query(By.css('#inputNote')).nativeElement; + const invalidString = 'string that is invalid !@#$%^&*()'; + input.value = invalidString; + expect(component.localState.nextDisabled).toBe(true); + const inputEvent = document.createEvent('Event'); + inputEvent.initEvent('input', false, true); + input.dispatchEvent(inputEvent); + fixture.detectChanges(); + expect(component.localState.nextDisabled).toBe(true); + expect(component.noteGroup.value.note).toBe(invalidString); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.ts new file mode 100644 index 0000000..4f9f403 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.component.ts @@ -0,0 +1,115 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { SummaryPageService } from './summary-page.service'; +import { ActivatedRoute } from '@angular/router'; +import { CreditApplication, Individual, TattleIndividual } from '../../../../shared/credit-application'; +import { FormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { SelectOptions } from '../../../../shared/options/select-options'; +import { LoanTitle } from 'src/app/shared/loan-type/loan-title'; + +@Component({ + selector: 'lw-summary-page', + templateUrl: './summary-page.component.html', + styleUrls: ['./summary-page.component.scss'] +}) +export class SummaryPageComponent implements OnInit, OnDestroy { + + public creditApplication: CreditApplication; + public primaryBorrower: Individual; + public primaryBorrowerTattleSpouse?: TattleIndividual; + public primaryBorrowerFullInfoSpouse?: Individual; + public coBorrower?: Individual; + public coBorrowerTattleSpouse?: TattleIndividual; + public coBorrowerFullInfoSpouse?: Individual; + public primaryContact: Array = []; + public modFee: Array<{"value" : any, "desc" : string}> = []; + public pageForm: FormGroup; + public noteGroup: FormGroup; + public additionalInfoGroup: FormGroup; + public statusSubscription: Subscription; + public readonly FORM_STATE_INVALID = 'INVALID'; + + public savingData = null; + + public localState = { + nextDisabled: true, + hasCoApplicant: false, + hasCoApplicantTattleSpouse: false, + hasCoApplicantFullInfoSpouse: false, + hasPrimaryTattleSpouse: false, + hasPrimaryFullInfoSpouse: false, + isHelocRateMod: false, + isTrailerMotorhome: false, + isPowerSport: false, + isVehicleDefault: false, + requireFullCollateral: false, + isBoat: false + }; + + constructor( + private summaryPageService: SummaryPageService, + private route: ActivatedRoute) { } + + public ngOnInit() { + this.pageForm = this.summaryPageService.createPageGroup(); + this.noteGroup = this.pageForm.get(SummaryPageService.NOTE_FORM_NAME); + this.additionalInfoGroup = this.pageForm.get(SummaryPageService.ADDITIONAL_INFO_FORM_NAME); + this.creditApplication = this.summaryPageService.getCreditApplication(); + this.updateIndividuals(this.creditApplication); + this.primaryContact = this.summaryPageService.buildPrimaryContactMethodList(this.primaryBorrower); + this.modFee = SelectOptions.modFeeOptions; + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + this.localState.isHelocRateMod = this.summaryPageService.equalsLoanType(LoanTitle.HELOC_RateMod); + this.localState.isTrailerMotorhome = this.summaryPageService.equalsLoanType(LoanTitle.TrailerMotorhome); + this.localState.isPowerSport = this.summaryPageService.equalsLoanType(LoanTitle.PowerSport); + this.localState.isVehicleDefault = this.summaryPageService.equalsLoanType(LoanTitle.Vehicle); + this.localState.isBoat = this.summaryPageService.equalsLoanType(LoanTitle.Boat); + this.localState.requireFullCollateral = this.summaryPageService.requireFullCollateral(); + } + + public ngOnDestroy() { + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public updateIndividuals(creditApplication: CreditApplication) { + this.primaryBorrower = creditApplication.fullInfoBorrowers[0]; + if (this.primaryBorrower.fullInfoSpouse) { + this.localState.hasPrimaryFullInfoSpouse = true; + this.primaryBorrowerFullInfoSpouse = this.primaryBorrower.fullInfoSpouse; + } else if (this.primaryBorrower.tattleSpouse) { + this.localState.hasPrimaryTattleSpouse = true; + this.primaryBorrowerTattleSpouse = this.primaryBorrower.tattleSpouse; + } + if (creditApplication.fullInfoBorrowers.length === 2) { + this.localState.hasCoApplicant = true; + this.coBorrower = creditApplication.fullInfoBorrowers[1]; + if (this.coBorrower.fullInfoSpouse) { + this.localState.hasCoApplicantFullInfoSpouse = true; + this.coBorrowerFullInfoSpouse = this.coBorrower.fullInfoSpouse; + } else if (this.coBorrower.tattleSpouse) { + this.localState.hasCoApplicantTattleSpouse = true; + this.coBorrowerTattleSpouse = this.coBorrower.tattleSpouse; + } + } + } + + public commandGoBack(newLoc: string) { + this.summaryPageService.navigateBack(newLoc, this.route); + } + + public commandSubmit() { + if (this.localState.nextDisabled) { + return; + } + this.summaryPageService.saveAndNavigate(this.noteGroup.value, this.additionalInfoGroup.value, this.route); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + } + + + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.service.spec.ts new file mode 100644 index 0000000..468673a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.service.spec.ts @@ -0,0 +1,80 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { SummaryPageService } from './summary-page.service'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { Individual } from 'src/app/shared/credit-application'; +import { apiTranslatorPreTranslate } from 'src/app/testing/shared-mocks/api-translator-pre-post'; +import { vehicleCreditApplication } from 'src/app/testing/shared-mocks/vehicle-credit-application.mock'; + +describe('Summary Page Service', () => { + + let service: SummaryPageService; + let applicationWizardService: ApplicationWizardService; + const mockIndividual: Individual = apiTranslatorPreTranslate.fullInfoBorrowers[0]; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(SummaryPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should get credit application information from application wizard service', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.callThrough(); + service.getCreditApplication(); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + }); + + it('should navigate back to a previous route', () => { + spyOn(router, 'navigate').and.callThrough().and.returnValue(true); + const route = new ActivatedRoute(); + service.navigateBack('residence', route); + expect(router.navigate).toHaveBeenCalledWith(['../step-3'], { relativeTo: route }); + }); + + it('should build primary contact method list', () => { + const list = service.buildPrimaryContactMethodList(mockIndividual); + expect(list).toEqual(['primary@example.com', '(555) 555-0134']); + }); + + it('should NOT show vehicle condition when loantype is NOT vehicle', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + const condition = service.showVehicleCondition(); + expect(condition).toBe(false); + }); + + it('should show vehicle condition when vehicle type is automobile', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(vehicleCreditApplication); + const condition = service.showVehicleCondition(); + expect(condition).toBe(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.service.ts new file mode 100644 index 0000000..e13f19a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/summary-page.service.ts @@ -0,0 +1,139 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { CreditApplication, Individual } from '../../../../shared/credit-application'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; +import { VehicleType } from 'src/app/shared/vehicle-type/vehicle-type'; +import { LoanCategory } from 'src/app/shared/loan-type/loan-category'; +import { FormatService } from 'src/app/shared/pipes/format.service'; + +@Injectable({ + providedIn: 'root' +}) + +export class SummaryPageService { + + public static readonly NOTE_FORM_NAME = 'noteGroup'; + public static readonly ADDITIONAL_INFO_FORM_NAME = 'additionalInfoGroup'; + public summaryLinks = { + loan: '../step-1', + personal: '../step-2', + residence: '../step-3', + employment: '../step-4', + coApplicantPersonal: '../co-applicant-personal', + coApplicantResidence: '../co-applicant-residential', + coApplicantFinancial: '../co-applicant-financial', + collateral: '../eq-collateral', + vehicle: '../ve-collateral', + applicantSpouse: '../applicant-spouse', + coApplicantSpouse: '../co-applicant-spouse', + applicantSpousePersonal: '../applicant-spouse-personal', + applicantSpouseResidential: '../applicant-spouse-residential', + applicantSpouseFinancial: '../applicant-spouse-financial', + coApplicantSpousePersonal: '../co-applicant-spouse-personal', + coApplicantSpouseResidential: '../co-applicant-spouse-residential', + coApplicantSpouseFinancial: '../co-applicant-spouse-financial' + }; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private router: Router, + private formBuilder: FormBuilder, + private formatService: FormatService) { } + + public getCreditApplication(): CreditApplication { + return this.applicationWizardService.getFullCreditApplication(); + } + + public saveAndNavigate(noteData: any, additionalInfoData: any, route: ActivatedRoute) { + this.creditApplicationService.saveSummaryInformation(additionalInfoData, noteData).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(route.routeConfig.path); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(route.routeConfig.path); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public navigateBack(newLoc: string, route: ActivatedRoute) { + const targetRoute = this.summaryLinks[newLoc]; + if (!targetRoute) { + throw new Error('Unknown location key ' + newLoc); + } + this.router.navigate([targetRoute], { relativeTo: route }); + } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + const noteGroup = this.formBuilder.group({}); + const additionalInfoGroup = this.formBuilder.group({}); + CommonGroups.SUMMARY_NOTE.fields.forEach((eachField) => { + noteGroup.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }, this); + CommonGroups.SUMMARY_ADDITIONAL_INFO.fields.forEach((eachField) => { + additionalInfoGroup.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }, this); + group.addControl(SummaryPageService.NOTE_FORM_NAME, noteGroup); + group.addControl(SummaryPageService.ADDITIONAL_INFO_FORM_NAME, additionalInfoGroup); + return group; + } + + public showVehicleCondition(): boolean { + const loanInfo = this.getCreditApplication(); + if (loanInfo.loanType.category === LoanCategory.Vehicle) { + switch (loanInfo.vehicleInformation.type) { + case VehicleType.AUTO: + case VehicleType.CYCLE: { + return true; + } + default: { + return false; + } + } + } + return false; + } + + public buildPrimaryContactMethodList(primaryBorrower: Individual): Array { + const primaryContactMethod: Array = []; + primaryContactMethod.push(primaryBorrower.personalInformation.email); + primaryContactMethod.push(primaryBorrower.personalInformation.primaryPhoneNumber); + + if (primaryBorrower.personalInformation.secondaryPhoneNumber) { + primaryContactMethod.push(primaryBorrower.personalInformation.secondaryPhoneNumber); + } + + if (primaryBorrower.employmentInformation) { + for (let i = 0; i < primaryBorrower.employmentInformation.length; i++) { + if (primaryBorrower.employmentInformation[i].workPhoneNumber) { + primaryContactMethod.push(primaryBorrower.employmentInformation[i].workPhoneNumber); + } + } + } + return primaryContactMethod; + } + + public equalsLoanType(loanType): boolean { + return this.applicationWizardService.getGroupValues('loanType').title === loanType; + } + + public requireFullCollateral(): boolean { + const loanInfo = this.getCreditApplication(); + const loanId = loanInfo.loanType.id; + return !( loanId === 'boat' || loanId === 'pwc' || loanId === 'tmh'); + } + + public formatState(state: string): string { + return this.formatService.formatState(state); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.html new file mode 100644 index 0000000..106a373 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.html @@ -0,0 +1,14 @@ + +
+
+
SUMMARY.TITLES.TATTLE + +
+
+
SUMMARY.PERSONAL.FULL_NAME
+
{{personalInformation.firstName}} {{personalInformation.middleInitial}} {{personalInformation.lastName}}
+
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.spec.ts new file mode 100644 index 0000000..a3dcaa3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.spec.ts @@ -0,0 +1,57 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { TattleSpouseSummaryComponent } from './tattle-spouse-summary.component'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Tattle Spouse Summary Component', () => { + let component: TattleSpouseSummaryComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + TattleSpouseSummaryComponent + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TattleSpouseSummaryComponent); + component = fixture.componentInstance; + component.personalInformation = { + firstName: 'John', + middleInitial: 'J', + lastName: 'Doe', + }; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit an event when the edit button is clicked', () => { + fixture.detectChanges(); + let eventHappened = false; + component.requestToEdit.subscribe((event) => { + eventHappened = true; + }); + const button = fixture.debugElement.query(By.css('button')).nativeElement; + button.click(); + fixture.detectChanges(); + expect(eventHappened).toEqual(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.ts new file mode 100644 index 0000000..24b5b8b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/tattle-spouse-summary.component.ts @@ -0,0 +1,25 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { TattleIndividual } from '../../../../shared/credit-application'; +import { TattleSpousePersonalInformation } from '../../../field-groups/tattle-spouse/tattle-spouse-personal-information'; + +@Component({ + selector: 'lw-tattle-spouse-summary', + templateUrl: './tattle-spouse-summary.component.html', + styleUrls: ['./summary-page.component.scss'] +}) +export class TattleSpouseSummaryComponent { + + @Input() + public personalInformation: TattleSpousePersonalInformation; + + @Output() + public requestToEdit: EventEmitter; + + constructor() { + this.requestToEdit = new EventEmitter(); + } + + public commandRequestEdit() { + this.requestToEdit.emit('edit'); // @TODO This may be an id later. + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.html new file mode 100644 index 0000000..6a5effb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.html @@ -0,0 +1,110 @@ + +
+
+
SUMMARY.TITLES.VEHICLE + +
+
+
SUMMARY.VEHICLE.TYPE
+ +
+
SUMMARY.VEHICLE.CONDITION
+
{{vehicleInformation.condition}}
+
SUMMARY.VEHICLE.MODEL
+
+ {{vehicleInformation.year}} {{vehicleInformation.make}} {{vehicleInformation.model}} +
+
SUMMARY.VEHICLE.MILEAGE
+
{{vehicleInformation.mileage}}
+ +
SUMMARY.VEHICLE.VALUE
+
{{vehicleInformation.value | currency}}
+ +
{{isPowerSport ? "FORMS.VEHICLE.VIN_POWERSPORT" : isBoat ? "FORMS.VEHICLE.BOAT_HULL" : "FORMS.VEHICLE.VIN"}}
+
{{vehicleInformation.vin}}
+ +
FORMS.VEHICLE.BOAT_LENGTH
+
{{vehicleInformation.boatLength}} feet
+ +
FORMS.VEHICLE.STATE_TITLED
+
{{localState.stateTitled}}
+ +
FORMS.VEHICLE.COUNTY_TITLED
+
{{vehicleInformation.countyTitled}}
+ +
FORMS.VEHICLE.LOAN_PURPOSE
+
{{localState.loanPurpose}}
+ +
SUMMARY.VEHICLE.TERM
+
{{vehicleInformation.term}} months
+ +
DNR Registration Card document(s)
+
{{vehicleInformation.dnrRegistrationCardFiles | uploadedFiles}}
+ +
FORMS.VEHICLE.BOAT_SAME_ADDRESS
+
{{vehicleInformation.boatSameAddress}}
+ +
SUMMARY.VEHICLE.BOAT_ADDRESS
+
{{vehicleInformation.boatAddress.addressLine1}} +
+ {{vehicleInformation.boatAddress.addressLine2}} +
+
+ {{vehicleInformation.boatAddress.city}}, {{vehicleInformation.boatAddress.state}} {{vehicleInformation.boatAddress.zipcode}} +
+ +
FORMS.VEHICLE.TRANSFERRING
+
{{vehicleInformation.transferring}}
+ +
FORMS.VEHICLE.LICENSE_PLATE_NUMBER
+
{{vehicleInformation.licensePlateNumber}}
+ +
{{isBoat ? "FORMS.VEHICLE.BOAT_SELLER" : "SUMMARY.VEHICLE.SELLER"}}
+
{{vehicleInformation.seller | vehicleSeller}}
+ +
FORMS.VEHICLE.TITLE_BRANDED
+
{{vehicleInformation.titleBranded}}
+ +
FORMS.VEHICLE.LIEN
+
{{vehicleInformation.lien}}
+ +
FORMS.VEHICLE.DEALER_NAME
+
{{vehicleInformation.dealerName}}
+ +
FORMS.VEHICLE.DEALER_PHONE
+
{{vehicleInformation.dealerPhone}}
+ +
Boat Title document(s)
+
{{vehicleInformation.boatTitleFiles | uploadedFiles}}
+ +
Purchase Contract document(s)
+
{{vehicleInformation.purchaseContractFiles | uploadedFiles}}
+ +
FORMS.VEHICLE.TRAILER_YEAR
+
{{vehicleInformation.trailerYear}}
+ +
FORMS.VEHICLE.TRAILER_MAKE
+
{{vehicleInformation.trailerMake}}
+ +
FORMS.VEHICLE.TRAILER_TYPE
+
{{localState.trailerType}}
+ +
Trailer Title document(s)
+
{{vehicleInformation.trailerTitleFiles | uploadedFiles}}
+ +
FORMS.VEHICLE.MOTOR_YEAR
+
{{vehicleInformation.motorYear}}
+ +
FORMS.VEHICLE.MOTOR_MAKE
+
{{vehicleInformation.motorMake}}
+ +
FORMS.VEHICLE.MOTOR_MODEL
+
{{vehicleInformation.motorModel}}
+ +
FORMS.VEHICLE.MOTOR_TYPE
+
{{localState.motorType}}
+
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.spec.ts new file mode 100644 index 0000000..87e2649 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.spec.ts @@ -0,0 +1,88 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { OccupancyTypePipe } from '../../../../shared/pipes/occupancy-type.pipe'; +import { BooleanStringPipe } from '../../../../shared/pipes/boolean-string.pipe'; +import { PropertyTypePipe } from '../../../../shared/pipes/property-type.pipe'; +import { VehicleSummaryComponent } from './vehicle-summary.component'; +import { VehicleSellerPipe } from '../../../../shared/pipes/vehicle-collateral.pipe'; +import { VehicleTypePipe } from '../../../../shared/pipes/vehicle-type.pipe'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { LoanPurpose } from 'src/app/shared/loan-type/loan-purpose'; +import { VehicleType } from 'src/app/shared/vehicle-type/vehicle-type'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; +import { UploadedFilesPipe } from 'src/app/shared/pipes/uploaded-files.pipe'; + +describe('VehicleSummaryComponent', () => { + let component: VehicleSummaryComponent; + let fixture: ComponentFixture; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + VehicleSummaryComponent, + OccupancyTypePipe, + BooleanStringPipe, + PropertyTypePipe, + VehicleSellerPipe, + VehicleTypePipe, + UploadedFilesPipe + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [ NO_ERRORS_SCHEMA ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(VehicleSummaryComponent); + component = fixture.componentInstance; + component.vehicleInformation = { + value: 5446, + condition: 'New', + type: VehicleType.AUTO, + year: 2015, + make: 'Honda', + model: 'Fit', + mileage: 65000, + term: '36', + loanPurpose: LoanPurpose.Refinance_Vehicle, + seller: 'Dealer', + vin: '00000000' + }; + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should emit an event when the edit button is clicked', () => { + fixture.detectChanges(); + let eventHappened = false; + component.requestToEdit.subscribe((event) => { + eventHappened = true; + }); + const button = fixture.debugElement.query(By.css('button')).nativeElement; + button.click(); + fixture.detectChanges(); + expect(eventHappened).toEqual(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.ts new file mode 100644 index 0000000..bf1912c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/summary/vehicle-summary.component.ts @@ -0,0 +1,63 @@ +import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; +import { VehicleOptions } from 'src/app/shared/options/vehicle-options'; +import { VehicleCollateralInformation } from '../../../field-groups/vehicle/vehicle-collateral-information'; +import { SummaryPageService } from './summary-page.service'; + +@Component({ + selector: 'lw-vehicle-summary', + templateUrl: './vehicle-summary.component.html', + styleUrls: ['./summary-page.component.scss'] +}) + +export class VehicleSummaryComponent implements OnInit { + + public localState = { + showVehicleCondition: false, + motorType: '', + trailerType: '', + stateTitled: '', + loanPurpose: '' + }; + + @Input() + public vehicleInformation: VehicleCollateralInformation; + + @Output() + public requestToEdit: EventEmitter; + + @Input() + public isTrailerMotorhome: boolean; + + @Input() + public isPowerSport: boolean; + + @Input() + public isVehicleDefault: boolean; + + @Input() + public isBoat: boolean; + + @Input() + public requireFullCollateral: boolean; + + constructor(private summaryPageService: SummaryPageService) { + this.requestToEdit = new EventEmitter(); + } + + public ngOnInit() { + this.updateLocalState(); + this.localState.motorType = this.vehicleInformation.motorType ? VehicleOptions.boatMotorTypeOptions.filter(x => x.value == this.vehicleInformation.motorType)[0].desc : ''; + this.localState.trailerType = this.vehicleInformation.trailerType ? VehicleOptions.boatTrailerTypeOptions.filter(x => x.value == this.vehicleInformation.trailerType)[0].desc : ''; + this.localState.loanPurpose = this.vehicleInformation.loanPurpose ? VehicleOptions.vehicleLoanPurposeOptionsVehicle.filter(x => x.value == this.vehicleInformation.loanPurpose)[0].desc : ''; + this.localState.stateTitled = this.vehicleInformation.stateTitled ? this.summaryPageService.formatState(this.vehicleInformation.stateTitled) : ''; + } + + public commandRequestEdit() { + this.requestToEdit.emit('edit'); // @TODO This may be an id later. + } + + public updateLocalState() { + this.localState.showVehicleCondition = this.summaryPageService.showVehicleCondition(); + } +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page.spec.ts new file mode 100644 index 0000000..8d70e24 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page.spec.ts @@ -0,0 +1,68 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoBorrowerTattleSpouseInfoPage } from './co-borrower-tattle-spouse-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; +import { configureTestSuite } from 'ng-bullet'; + +describe('CoBorrower Tattle Spouse Info Page', () => { + + let page: CoBorrowerTattleSpouseInfoPage; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new CoBorrowerTattleSpouseInfoPage('PAGETITLES.COMMON.COAPP_TATTLESPOUSE', + 'co-applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO, + CommonGroups.IS_ADDRESS_SAME_INFO + ]); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.TattleSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.FullInfoSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page.ts new file mode 100644 index 0000000..bd4b4a4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page.ts @@ -0,0 +1,15 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { PrimaryTattleSpouseInfoPage } from './primary-tattle-spouse-info-page'; +import { SpouseType } from 'src/app/shared/credit-application'; + +export class CoBorrowerTattleSpouseInfoPage extends PrimaryTattleSpouseInfoPage { + + public getBorrowerType(): BorrowerType { + return BorrowerType.CO_BORROWER; + } + + public getSpouseBorrowerType(): BorrowerType { + return BorrowerType.CO_BORROWER_SPOUSE; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/primary-tattle-spouse-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/primary-tattle-spouse-info-page.spec.ts new file mode 100644 index 0000000..b04087d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/primary-tattle-spouse-info-page.spec.ts @@ -0,0 +1,93 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../../../shared/loan-type'; +import { CommonGroups } from '../../../../field-groups/common-groups'; +import { TranslateModule } from '@ngx-translate/core'; +import { PrimaryTattleSpouseInfoPage } from './primary-tattle-spouse-info-page'; +import { SpouseType } from '../../../../../shared/credit-application'; +import { BorrowerType } from '../../../../../shared/borrower-type'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + +describe('Primary Tattle Spouse Info Page', () => { + + let page: PrimaryTattleSpouseInfoPage; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new PrimaryTattleSpouseInfoPage('PAGETITLES.COMMON.PRIMARY_TATTLESPOUSE', + 'applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO, + CommonGroups.IS_ADDRESS_SAME_INFO + ]); + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + + it('should determine that a page is required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.TattleSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(true); + }); + + it('should determine that a page is NOT required', () => { + spyOn(applicationWizardService, 'isApplicantNonSpouse').and.callThrough().and.returnValue(true); + spyOn(applicationWizardService, 'getSpouseType').and.callThrough().and.returnValue(SpouseType.FullInfoSpouse); + + const isRequired: boolean = page.determinePageRequired(applicationWizardService); + expect(applicationWizardService.isApplicantNonSpouse).toHaveBeenCalled(); + expect(applicationWizardService.getSpouseType).toHaveBeenCalled(); + expect(isRequired).toBe(false); + }); + + + it('should always return primary borrower type', () => { + spyOn(page, 'getBorrowerType').and.callThrough(); + const borrowerType = page.getBorrowerType(); + expect(page.getBorrowerType).toHaveBeenCalled(); + expect(borrowerType).toBe(BorrowerType.PRIMARY_BORROWER); + }); + + it('should always return spouse borrower type', () => { + spyOn(page, 'getSpouseBorrowerType').and.callThrough(); + const borrowerType = page.getSpouseBorrowerType(); + expect(page.getSpouseBorrowerType).toHaveBeenCalled(); + expect(borrowerType).toBe(BorrowerType.PRIMARY_BORROWER_SPOUSE); + }); + + it('should correctly determine if page is valid ( default invalid )', () => { + spyOn(page, 'isValidPage').and.callThrough(); + const errors = page.isValidPage(applicationWizardService, creditApplicationService); + expect(page.isValidPage).toHaveBeenCalled(); + expect(errors.length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/primary-tattle-spouse-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/primary-tattle-spouse-info-page.ts new file mode 100644 index 0000000..90fe301 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/pages/primary-tattle-spouse-info-page.ts @@ -0,0 +1,44 @@ +import { ApplicationWizardService } from '../../../../application-wizard.service'; +import { ApplicationPage } from '../../../../application-page'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { TattleSpousePersonalInformation } from '../../../../field-groups/tattle-spouse/tattle-spouse-personal-information'; +import { Individual, SpouseType } from '../../../../../shared/credit-application'; +import { CreditApplicationService } from '../../../../../shared/credit-application.service'; + +export class PrimaryTattleSpouseInfoPage extends ApplicationPage { + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + const spouseType = applicationWizardService.getSpouseType(this.getBorrowerType()); + const needsSpouse = applicationWizardService.isApplicantNonSpouse(this.getBorrowerType()); + if (!spouseType && needsSpouse) { + // This condition gives us a nice default topic message for the NEXT button + return true; + } + return needsSpouse && spouseType === SpouseType.TattleSpouse; + } + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + let value; + const errors = []; + const borrowerType = this.getSpouseBorrowerType(); + + const personalInfo: TattleSpousePersonalInformation = + applicationWizardService.getIndividualGroupValues('personalInformation', borrowerType); + this.fieldgroup.forEach((pageFieldGroup) => { + pageFieldGroup.fields.forEach((pageField) => { + value = personalInfo[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }, this); + }, this); + return errors; + } + + public getBorrowerType(): BorrowerType { + return BorrowerType.PRIMARY_BORROWER; + } + + public getSpouseBorrowerType(): BorrowerType { + return BorrowerType.PRIMARY_BORROWER_SPOUSE; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.html new file mode 100644 index 0000000..92140f4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.html @@ -0,0 +1,35 @@ +
+
+
+

HEADINGS.SPOUSE.{{localState.borrowerType}}

+
+ +
COPY.SPOUSE.{{localState.borrowerType}}
+
+
+
+ + + + + + + + +
+
+ + +
+
+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.spec.ts new file mode 100644 index 0000000..645287a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.spec.ts @@ -0,0 +1,153 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { TattleSpouseInfoPageComponent } from './tattle-spouse-info-page.component'; +import { TattleSpouseInfoPageService } from './tattle-spouse-info-page.service'; +import { TattleIndividual } from '../../../../shared/credit-application'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { configureTestSuite } from 'ng-bullet'; +import { AddressInformation } from 'src/app/loan-wizard/field-groups/address/address-information'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; + +describe('TattleSpouseInfoPageComponent', () => { + let component: TattleSpouseInfoPageComponent; + let fixture: ComponentFixture; + let service: TattleSpouseInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + TattleSpouseInfoPageComponent + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [ + NO_ERRORS_SCHEMA + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TattleSpouseInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(TattleSpouseInfoPageService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.isAddressSameGroup).toBeTruthy(); + expect(component.addressInfoGroup).not.toBeTruthy(); + }); + + it('should not allow progress if invalid', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.localState.nextDisabled).toEqual(true); + spyOn(service, 'saveAndNavigate').and.callThrough(); + component.commandNext(); + expect( service.saveAndNavigate).not.toHaveBeenCalled(); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + expect(component.commandPrevious).not.toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + fixture.detectChanges(); + spyOn(service, 'saveAndNavigate'); + component.localState.nextDisabled = false; + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + spyOn(service, 'cancelAndNavigate'); + component.commandPrevious(); + expect(service.cancelAndNavigate).toHaveBeenCalled(); + }); + + it('should bind the previous button to the correct command function', () => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonPrevious')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).not.toHaveBeenCalled(); + expect(component.commandPrevious).toHaveBeenCalled(); + }); + }); + + it('should patch previous values onInit', () => { + const tattleSpouseInfo: TattleIndividual = creditApplicationService.generateDefaultTattleSpouse(); + spyOn(service, 'getApplicationGroupValues').and.returnValue(tattleSpouseInfo); + fixture.detectChanges(); + expect(component.personalInfoGroup.value).toEqual(tattleSpouseInfo.personalInformation); + expect(component.isAddressSameGroup.value).toEqual({isAddressSame: CreditApplicationValues.CHOICE_YES}); + }); + + it('should add address group', () => { + const tattleSpouseInfo: TattleIndividual = creditApplicationService.generateDefaultTattleSpouse(); + tattleSpouseInfo.hasSameAddressAsSpouse = CreditApplicationValues.CHOICE_NO; + spyOn(service, 'getApplicationGroupValues').and.returnValue(tattleSpouseInfo); + fixture.detectChanges(); + expect(component.addressInfoGroup.value).toEqual(tattleSpouseInfo.addressInformation); + expect(component.personalInfoGroup.value).toEqual(tattleSpouseInfo.personalInformation); + expect(component.isAddressSameGroup.value).toEqual({isAddressSame: CreditApplicationValues.CHOICE_NO}); + }); + + it('should submit the USPS Verified Address Value if the match was saved', () => { + spyOn(service, 'saveAndNavigate'); + fixture.detectChanges(); + component.isAddressSameGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_NO}); + + const mockAddressInput: AddressInformation = { + addressLine1: '51 Gardner', + addressLine2: '', + city: 'Alston', + state: 'MA', + zipcode: '54545', + }; + const mockUSPSAddress: AddressInformation = { + addressLine1: mockAddressInput.addressLine1.toLocaleUpperCase(), + addressLine2: mockAddressInput.addressLine2.toLocaleUpperCase(), + city: mockAddressInput.city.toLocaleUpperCase(), + state: mockAddressInput.state.toLocaleUpperCase(), + zipcode: mockAddressInput.zipcode, + zipcodeExt: '5446' + }; + + component.addressInfoGroup.patchValue(mockAddressInput); + component.confirmMailingAddressInformation(mockUSPSAddress); + const fields = component.makeTattleIndividual(); + expect(fields.addressInformation).toEqual(mockUSPSAddress); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.ts new file mode 100644 index 0000000..20e5ca9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.component.ts @@ -0,0 +1,172 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { TattleSpouseInfoPageService } from './tattle-spouse-info-page.service'; +import { TattleIndividual } from '../../../../shared/credit-application'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { TattleSpousePersonalInformation } from '../../../field-groups/tattle-spouse/tattle-spouse-personal-information'; +import { AddressInformation } from '../../../field-groups/address/address-information'; +import { AddressFormBuilderService } from 'src/app/shared/address-validation/address-form-builder.service'; +import { AddressDiffInformation } from 'src/app/loan-wizard/field-groups/address-compare/address-compare-information'; +import { AddressValidationFormatConverter } from 'src/app/shared/address-validation/address-validation-format-converter'; + +@Component({ + selector: 'lw-tattle-spouse-info-page', + templateUrl: './tattle-spouse-info-page.component.html' +}) +export class TattleSpouseInfoPageComponent implements OnInit, OnDestroy { + + public pageForm: FormGroup; + public isAddressSameGroup: FormGroup; + public addressInfoGroup: FormGroup; + public personalInfoGroup: FormGroup; + public mailingAddressVerifyInfoGroup: FormGroup; + private INDIVIDUAL_REF: BorrowerType; + private readonly FORM_STATE_INVALID = 'INVALID'; + private valueSubscription: Subscription; + private statusSubscription: Subscription; + + public savingData = null; + + public localState = { + showAddress: false, + nextDisabled: true, + previousDisabled: false, + nextTopic: '', + borrowerType: '', + mailingAddressInformation: null, + showSpouseDisclosure: true + }; + + constructor( + private tattleSpouseInfoPageService: TattleSpouseInfoPageService, + private route: ActivatedRoute) { } + + public ngOnDestroy() { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public ngOnInit() { + if (this.route.snapshot.data[0]) { + this.INDIVIDUAL_REF = this.route.snapshot.data[0].individual; + } else { + this.INDIVIDUAL_REF = BorrowerType.PRIMARY_BORROWER_SPOUSE; + } + this.localState.borrowerType = this.INDIVIDUAL_REF; + this.pageForm = this.tattleSpouseInfoPageService.createPageGroup(); + this.isAddressSameGroup = this.pageForm.get(TattleSpouseInfoPageService.IS_SAME_ADDRESS_FORM_NAME); + this.personalInfoGroup = this.pageForm.get(TattleSpouseInfoPageService.PERSONAL_FORM_NAME); + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + this.patchPreviousValues(); + } + + public patchPreviousValues() { + const previousValues: TattleIndividual = this.tattleSpouseInfoPageService.getApplicationGroupValues(this.INDIVIDUAL_REF); + if (previousValues.hasSameAddressAsSpouse) { + this.isAddressSameGroup.patchValue({isAddressSame: previousValues.hasSameAddressAsSpouse}); + } + this.personalInfoGroup.patchValue(previousValues.personalInformation); + this.updateLocalState(this.pageForm.value); + } + + public updateLocalState(modelValue: any): void { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + + const value = modelValue[TattleSpouseInfoPageService.IS_SAME_ADDRESS_FORM_NAME]; + this.localState = { + showAddress: this.tattleSpouseInfoPageService.getRequiredAddress(value.isAddressSame), + previousDisabled: this.localState.previousDisabled, + nextDisabled: this.localState.nextDisabled, + nextTopic: this.tattleSpouseInfoPageService.getNextTopicMessage(this.INDIVIDUAL_REF), + borrowerType: this.localState.borrowerType, + mailingAddressInformation: this.localState.mailingAddressInformation ? + this.localState.mailingAddressInformation : + this.tattleSpouseInfoPageService.getApplicationGroupValues( + this.INDIVIDUAL_REF)['addressInformation'], + showSpouseDisclosure: this.localState.borrowerType === BorrowerType.PRIMARY_BORROWER_SPOUSE || + this.localState.borrowerType === BorrowerType.CO_BORROWER_SPOUSE + }; + this.updateAddressesForm(); + this.valueSubscription = this.pageForm.valueChanges.subscribe(() => { + this.updateLocalState(this.pageForm.value); + }); + } + + public confirmMailingAddressInformation(addressInformation: AddressInformation) { + if (!this.mailingAddressVerifyInfoGroup) { + AddressFormBuilderService.addAddressCompareGroupIfMissing( + this.pageForm, TattleSpouseInfoPageService.MAILING_ADDRESS_VERIFY_FORM_NAME + ); + this.mailingAddressVerifyInfoGroup = this.pageForm.get(TattleSpouseInfoPageService.MAILING_ADDRESS_VERIFY_FORM_NAME); + } + if (!addressInformation) { + this.mailingAddressVerifyInfoGroup.patchValue({addressVerifyStatus: false}); + } else { + this.mailingAddressVerifyInfoGroup.patchValue({addressVerifyStatus: true}); + const addressDiff: AddressDiffInformation = AddressValidationFormatConverter.getAddressValidationDiff( + AddressValidationFormatConverter.makeApiAddress(this.addressInfoGroup.value), + addressInformation + ); + if (!addressDiff.allFieldsSame) { + this.addressInfoGroup.patchValue(addressInformation); + } + } + this.localState.mailingAddressInformation = addressInformation; + } + + public makeTattleIndividual(): TattleIndividual { + const personalInformation: TattleSpousePersonalInformation = this.pageForm.value[TattleSpouseInfoPageService.PERSONAL_FORM_NAME]; + let addressInformation: AddressInformation; + if (this.localState.mailingAddressInformation) { + addressInformation = this.localState.mailingAddressInformation; + } else if (this.addressInfoGroup) { + addressInformation = this.addressInfoGroup.value; + } + return { + personalInformation: personalInformation, + addressInformation: addressInformation, + hasSameAddressAsSpouse: this.isAddressSameGroup.value.isAddressSame + }; + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + this.tattleSpouseInfoPageService.saveAndNavigate(this.makeTattleIndividual(), this.route, this.INDIVIDUAL_REF); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + } + + public commandPrevious() { + this.tattleSpouseInfoPageService.cancelAndNavigate(this.route); + } + + public updateAddressesForm(): void { + if (this.localState.showAddress && !this.addressInfoGroup) { + this.tattleSpouseInfoPageService.addAddressGroupsIfMissing(this.pageForm); + this.addressInfoGroup = this.pageForm.get(TattleSpouseInfoPageService.ADDRESS_FORM_NAME); + this.pageForm.addControl(TattleSpouseInfoPageService.ADDRESS_FORM_NAME, this.addressInfoGroup); + const prevValue: AddressInformation = this.tattleSpouseInfoPageService.getApplicationGroupValues( + this.INDIVIDUAL_REF)['addressInformation']; + if (prevValue.state) { + this.addressInfoGroup.patchValue(prevValue); + this.localState.mailingAddressInformation = prevValue; + } + } else if (!this.localState.showAddress && this.addressInfoGroup) { + this.addressInfoGroup = null; + this.mailingAddressVerifyInfoGroup = null; + this.tattleSpouseInfoPageService.removeAddressGroups(this.pageForm); + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.service.spec.ts new file mode 100644 index 0000000..d6d7a6d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.service.spec.ts @@ -0,0 +1,105 @@ +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../../testing/shared-mocks/credit-application-dao.mock.service'; +import {NO_ERRORS_SCHEMA} from '@angular/core'; +import { LoanTypeSet } from '../../../../shared/loan-type'; +import { TattleSpouseInfoPageService } from './tattle-spouse-info-page.service'; + +import { BorrowerType } from '../../../../shared/borrower-type'; +import { TattleIndividual } from '../../../../shared/credit-application'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +describe('Tattle Spouse\'s Info Page Service', () => { + + let service: TattleSpouseInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let creditApplicationService: CreditApplicationService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [ + NO_ERRORS_SCHEMA + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(TattleSpouseInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + creditApplicationService = TestBed.get(CreditApplicationService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create the top level form group', () => { + spyOn(service, 'createTattleSpousePersonalInformationGroup').and.callThrough(); + const pageGroup = service.createPageGroup(); + const personalGroup = pageGroup.get(TattleSpouseInfoPageService.PERSONAL_FORM_NAME); + expect(service.createTattleSpousePersonalInformationGroup).toHaveBeenCalled(); + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO.fields.forEach((eachField) => { + expect(personalGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should create an address info group', () => { + const addressGroup = service.createAddressInformationGroup(); + expect(addressGroup).toBeTruthy(); + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + expect(addressGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should navigate the application, on successful save', () => { + spyOn(creditApplicationService, 'saveTattleSpouseInformation').and.returnValue(of(true)); + spyOn(router, 'navigate'); + const info = {}; + const borrower = BorrowerType.PRIMARY_BORROWER_SPOUSE; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute, borrower); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'saveTattleSpouseInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + const info = {}; + const borrower = BorrowerType.PRIMARY_BORROWER_SPOUSE; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute, borrower); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('cancelling should always navigate', () => { + spyOn(router, 'navigate'); + const activatedRoute = {}; + service.cancelAndNavigate(activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.service.ts new file mode 100644 index 0000000..6f4dfc0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/common/tattle-spouse/tattle-spouse-info-page.service.ts @@ -0,0 +1,139 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { ApplicationWizardService } from '../../../application-wizard.service'; +import { CommonGroups } from '../../../field-groups/common-groups'; +import { PageService } from '../../page-service'; +import { CreditApplicationValues } from '../../../../shared/credit-application-values'; +import { TattleSpousePersonalInformation } from '../../../field-groups/tattle-spouse/tattle-spouse-personal-information'; +import { BorrowerType } from 'src/app/shared/borrower-type'; +import { AddressInformation } from '../../../field-groups/address/address-information'; +import { TattleIndividual } from '../../../../shared/credit-application'; +import { AddressStatusPipe } from '../../../../shared/pipes/address-status.pipe'; +import { CreditApplicationService } from '../../../../shared/credit-application.service'; + +@Injectable({ + providedIn: 'root' +}) +export class TattleSpouseInfoPageService implements PageService { + + public static readonly ADDRESS_FORM_NAME = 'spouseAddressInformationGroup'; + public static readonly IS_SAME_ADDRESS_FORM_NAME = 'isAddressSameGroup'; + public static readonly PERSONAL_FORM_NAME = 'spousePersonalInformationGroup'; + public static readonly MAILING_ADDRESS_VERIFY_FORM_NAME = 'mailingAddressCompareInformationGroup'; + private CURRENT_ROUTE = 'applicant-spouse'; + public readonly MODEL_REF = 'nonApplicantAddressSpouseInformation'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private router: Router, + private formBuilder: FormBuilder) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + + const memberGroup = this.createIsSameAddressInformationGroup(); + group.addControl(TattleSpouseInfoPageService.IS_SAME_ADDRESS_FORM_NAME, memberGroup); + const personalGroup = this.createTattleSpousePersonalInformationGroup(); + group.addControl(TattleSpouseInfoPageService.PERSONAL_FORM_NAME, personalGroup); + + return group; + } + + public createIsSameAddressInformationGroup(): FormGroup { + const memberGroup = this.formBuilder.group({}); + + CommonGroups.IS_ADDRESS_SAME_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; + } + + public createTattleSpousePersonalInformationGroup(): FormGroup { + const personalGroup = this.formBuilder.group({}); + + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO.fields.forEach((eachField) => { + personalGroup.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }, this); + + return personalGroup; + } + + public addAddressGroupsIfMissing(group: FormGroup) { + if (group.get(TattleSpouseInfoPageService.ADDRESS_FORM_NAME)) { + return; + } + const addressGroup = this.createAddressInformationGroup(); + + group.addControl(TattleSpouseInfoPageService.ADDRESS_FORM_NAME, addressGroup); + } + + public removeAddressGroups(group: FormGroup) { + if (!group.get(TattleSpouseInfoPageService.ADDRESS_FORM_NAME)) { + return; + } + group.removeControl(TattleSpouseInfoPageService.ADDRESS_FORM_NAME); + group.removeControl(TattleSpouseInfoPageService.MAILING_ADDRESS_VERIFY_FORM_NAME); + } + + public createAddressInformationGroup(): FormGroup { + const memberGroup = this.formBuilder.group({}); + + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }, this); + + return memberGroup; + } + + public getApplicationGroupValues(individual: BorrowerType): TattleIndividual { + return { + personalInformation: this.applicationWizardService.getIndividualGroupValues('personalInformation', individual), + addressInformation: this.applicationWizardService.getIndividualGroupValues('addressInformation', individual), + hasSameAddressAsSpouse: this.applicationWizardService.getIndividualGroupValues('hasSameAddressAsSpouse', individual) + }; + } + + public saveAndNavigate(tattleSpouseInformation: TattleIndividual, route: ActivatedRoute, individual: BorrowerType) { + this.CURRENT_ROUTE = this.determineCurrentRoute(individual); + this.creditApplicationService.saveTattleSpouseInformation(tattleSpouseInformation, individual).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public determineCurrentRoute(individual: BorrowerType): string { + switch (individual) { + case BorrowerType.PRIMARY_BORROWER_SPOUSE: + return 'applicant-spouse'; + case BorrowerType.CO_BORROWER_SPOUSE: + return 'co-applicant-spouse'; + } + } + + public cancelAndNavigate(route: ActivatedRoute) { + const nextRoute = this.applicationWizardService.getPreviousRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + + public getNextTopicMessage(individual: BorrowerType): string { + this.CURRENT_ROUTE = this.determineCurrentRoute(individual); + return this.applicationWizardService.getNextTopicMessage(this.CURRENT_ROUTE); + } + + public getRequiredAddress(isAddressSame: string): boolean { + return isAddressSame === CreditApplicationValues.CHOICE_NO; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-info-page.ts new file mode 100644 index 0000000..c8c4dd8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-info-page.ts @@ -0,0 +1,24 @@ +import { ApplicationPage } from '../../application-page'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { DisclosureInformation } from '../../field-groups/disclosures/credit-card-disclosure'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; + +export class CreditCardDisclosuresInfoPage extends ApplicationPage { + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + let value; + const errors = []; + + const disclosureInfo: DisclosureInformation = applicationWizardService.getGroupValues('disclosureInformation'); + + this.fieldgroup.forEach((pageFieldGroup) => { + pageFieldGroup.fields.forEach((pageField) => { + value = disclosureInfo[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }, this); + }, this); + return errors; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.html new file mode 100644 index 0000000..196cf58 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.html @@ -0,0 +1,31 @@ +
+
+
+

HEADINGS.DISCLOSURES

+
+
+ + + + + +
+

DISCLOSURE.HELOC.CONFIRM

+
+
+
+ +
+ + +
+
+ + +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.spec.ts new file mode 100644 index 0000000..9adc0d0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.spec.ts @@ -0,0 +1,85 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditCardDisclosuresPageComponent } from './credit-card-disclosures-page.component'; +import { AddressInfoFormComponent } from '../../field-groups/address/address-info-form.component'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { AddressInfoPageService } from '../common/address/address-info-page.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { By } from '@angular/platform-browser'; +import { StatePipe } from '../../../shared/pipes/state.pipe'; +import { DisclosureInformation } from '../../field-groups/disclosures/credit-card-disclosure'; +import { configureTestSuite } from 'ng-bullet'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + + +describe('CreditCardDisclosuresPageComponent', () => { + let component: CreditCardDisclosuresPageComponent; + let fixture: ComponentFixture; + let service: AddressInfoPageService; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + CreditCardDisclosuresPageComponent, + AddressInfoFormComponent, + StatePipe + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [ NO_ERRORS_SCHEMA ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CreditCardDisclosuresPageComponent); + component = fixture.componentInstance; + service = TestBed.get(AddressInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should bind the previous button to the correct command function', () => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + fixture.detectChanges(); + + const button = fixture.debugElement.query(By.css('#buttonPrevious')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).not.toHaveBeenCalled(); + expect(component.commandPrevious).toHaveBeenCalled(); + }); + }); + + it('should patch page data onInit', () => { + const disclosures: DisclosureInformation = { + truthful: false, + contact: true + }; + spyOn(service, 'getApplicationGroupValues').and.returnValue(disclosures); + fixture.detectChanges(); + expect(component.disclosureGroup.value).toEqual(disclosures); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.ts new file mode 100644 index 0000000..518a592 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { CreditCardDisclosuresPageService } from './credit-card-disclosures-page.service'; +import { ApplicationWizardService } from '../../application-wizard.service'; + +@Component({ + selector: 'lw-credit-card-disclosures-page', + templateUrl: './credit-card-disclosures-page.component.html' +}) +export class CreditCardDisclosuresPageComponent implements OnInit { + + public savingData = null; + + public localState = { + previousDisabled: false, + nextDisabled: true, + cardName: '', + nextTopic: '', + showMarriedDisclosure: false, + isHeloc: false + }; + public pageForm: FormGroup; + public disclosureGroup: FormGroup; + private readonly FORM_STATE_INVALID = 'INVALID'; + + constructor( + private creditCardDisclosuresPageService: CreditCardDisclosuresPageService, + private applicationWizardService: ApplicationWizardService, + private route: ActivatedRoute) { } + + public ngOnInit() { + this.pageForm = this.creditCardDisclosuresPageService.createPageGroup(); + this.disclosureGroup = this.pageForm.get(CreditCardDisclosuresPageService.DISCLOSURE_FORM_NAME); + this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + this.disclosureGroup.patchValue(this.creditCardDisclosuresPageService + .getApplicationGroupValues(CreditCardDisclosuresPageService.DISCLOSURE_FORM_NAME)); + this.localState.nextTopic = this.creditCardDisclosuresPageService.getNextTopicMessage(); + this.localState.showMarriedDisclosure = this.creditCardDisclosuresPageService.determineShowMarriedDisclosure(); + this.localState.isHeloc = this.creditCardDisclosuresPageService.isHeloc(); + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + this.creditCardDisclosuresPageService.saveAndNavigate(this.pageForm.value.disclosureInformation, this.route); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + } + + public commandPrevious() { + this.creditCardDisclosuresPageService.cancelAndNavigate(this.route); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service.spec.ts new file mode 100644 index 0000000..606173c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service.spec.ts @@ -0,0 +1,58 @@ +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CreditCardDisclosuresPageService } from './credit-card-disclosures-page.service'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('Credit Card Disclosures Page Service', () => { + + let service: CreditCardDisclosuresPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(CreditCardDisclosuresPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('cancelling should always navigate', () => { + spyOn(router, 'navigate'); + const activatedRoute = {}; + service.cancelAndNavigate(activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service.ts new file mode 100644 index 0000000..fc5bf2b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service.ts @@ -0,0 +1,105 @@ +import { Injectable } from '@angular/core'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { AddressInformation } from '../../field-groups/address/address-information'; +import { DisclosureInformation } from '../../field-groups/disclosures/credit-card-disclosure'; +import { PageService } from '../page-service'; +import { CreditApplicationValues } from '../../../shared/credit-application-values'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; +import {LoanTitle} from '../../../shared/loan-type/loan-title'; +import { environment } from 'src/environments/environment'; +import { Observable, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; + + +@Injectable({ + providedIn: 'root' +}) +export class CreditCardDisclosuresPageService implements PageService { + + public static readonly DISCLOSURE_FORM_NAME = 'disclosureInformation'; + private readonly CURRENT_ROUTE = 'cc-disclosures'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private router: Router, + private formBuilder: FormBuilder, + private httpClient: HttpClient) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + + const disclosureGroup = this.createDisclosureInformationGroup(); + group.addControl('disclosureInformation', disclosureGroup); + + return group; + } + + public createDisclosureInformationGroup(): FormGroup { + const disclosureGroup = this.formBuilder.group({}); + + CommonGroups.CC_DISCLOSURE_INFO.fields.forEach((eachField) => { + disclosureGroup.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }, this); + + return disclosureGroup; + } + + public getApplicationGroupValues(groupName: string): any { + return this.applicationWizardService.getGroupValues(groupName); + } + + public saveAndNavigate(disclosureInformation: DisclosureInformation, route: ActivatedRoute) { + this.creditApplicationService.saveDisclosureInformation(disclosureInformation).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], { relativeTo: route }); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], { relativeTo: route }); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public cancelAndNavigate(route: ActivatedRoute) { + const prevRoute = this.applicationWizardService.getPreviousRoute(this.CURRENT_ROUTE); + this.router.navigate([prevRoute], { relativeTo: route }); + } + + public getNextTopicMessage(): string { + return this.applicationWizardService.getNextTopicMessage(this.CURRENT_ROUTE); + } + + public determineShowMarriedDisclosure(): boolean { + return this.creditApplicationService.isMarriedApplicant(); + } + + public isHeloc(): boolean { + return this.applicationWizardService.getGroupValues('loanType').title === LoanTitle.HELOC; + } + + public buildGetPdfImportantTermsURL(): string { + return environment.urlBase + '/GetPdf/ImportantTerms'; + } + + public buildGetPdfWhatYouShouldKnowURL(): string { + return environment.urlBase + '/GetPdf/WhatYouShouldKnow'; + } + + public requestImportantTermsPdf(): Observable { + return this.httpClient.get(this.buildGetPdfImportantTermsURL(), {responseType: "blob" as "json"}) + } + + public requestWhatYouShouldKnowPdf(): Observable { + return this.httpClient.get(this.buildGetPdfWhatYouShouldKnowURL(), {responseType: "blob" as "json"}) + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-outline.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-outline.ts new file mode 100644 index 0000000..ce1271b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-outline.ts @@ -0,0 +1,144 @@ +import { ApplicationPage } from '../../application-page'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { MemberInfoPage } from '../common/member/member-info-page'; +import { PrimaryEmploymentInfoPage } from '../common/employment/pages/primary-employment-info-page'; +import { PrimaryPersonalInfoPage } from '../common/personal/pages/primary-personal-info-page'; +import { PrimaryAddressInfoPage } from '../common/address/pages/primary-address-info-page'; +import { CreditCardDisclosuresInfoPage } from './credit-card-disclosures-info-page'; +import { PrimaryTattleSpouseInfoPage } from '../common/tattle-spouse/pages/primary-tattle-spouse-info-page'; +import { SpouseTypeInfoPage } from '../common/spouse-type/spouse-type-info-page'; +import { PrimarySpousePersonalInfoPage } from '../common/personal/pages/primary-spouse-personal-info-page'; +import { CoBorrowerPersonalInfoPage } from '../common/personal/pages/co-borrower-personal-info-page'; +import { CoBorrowerSpousePersonalInfoPage } from '../common/personal/pages/co-borrower-spouse-personal-info-page'; +import { PrimarySpouseAddressInfoPage } from '../common/address/pages/primary-spouse-address-info-page'; +import { CoBorrowerAddressInfoPage } from '../common/address/pages/co-borrower-address-info-page'; +import { CoBorrowerSpouseAddressInfoPage } from '../common/address/pages/co-borrower-spouse-address-info-page'; +import { PrimarySpouseEmploymentInfoPage } from '../common/employment/pages/primary-spouse-employment-info-page'; +import { CoBorrowerEmploymentInfoPage } from '../common/employment/pages/co-borrower-employment-info-page'; +import { CoBorrowerSpouseEmploymentInfoPage } from '../common/employment/pages/co-borrower-spouse-employment-info-page'; +import { CoBorrowerTattleSpouseInfoPage } from '../common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page'; +import { SummaryInfoPage } from '../common/summary/summary-info-page'; +import { DecisionPage } from '../common/decision/decision-page'; +import { ProcessingPage } from '../common/processing/processing-page'; + +export enum PageInfo { + PRIMARY, + PRIMARY_SPOUSE, + CO_APPLICANT, + CO_APPLICANT_SPOUSE +} + +export const CreditCardPages: Array = [ + /* PRIMARY */ + new MemberInfoPage('PAGETITLES.COMMON.INTRO', + 'step-1', + [ + CommonGroups.MEMBER_INFO_CC + ]), + new PrimaryPersonalInfoPage('PAGETITLES.COMMON.PERSONAL_INFO', + 'step-2', + [ + CommonGroups.PERSONAL_INFO + ]), + new PrimaryAddressInfoPage('PAGETITLES.COMMON.ADDRESS_INFO', + 'step-3', + [ + CommonGroups.ADDRESS_INFO + ]), + new PrimaryEmploymentInfoPage('PAGETITLES.COMMON.EMPLOYMENT_INFO', + 'step-4', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* PRIMARY_SPOUSE */ + new SpouseTypeInfoPage('PAGETITLES.COMMON.PRIMARY_SPOUSETYPE', + 'applicant-spouse-type', + [ + CommonGroups.IS_FULL_INFO_SPOUSE + ]), + new PrimaryTattleSpouseInfoPage('PAGETITLES.COMMON.PRIMARY_TATTLESPOUSE', + 'applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO + ]), + new PrimarySpousePersonalInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_PERSONAL_INFO', + 'applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new PrimarySpouseAddressInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_ADDRESS_INFO', + 'applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new PrimarySpouseEmploymentInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_EMPLOYMENT_INFO', + 'applicant-spouse-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* CO_APPLICANT */ + new CoBorrowerPersonalInfoPage('PAGETITLES.COMMON.COAPP_PERSONAL_INFO', + 'co-applicant-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new CoBorrowerAddressInfoPage('PAGETITLES.COMMON.COAPP_ADDRESS_INFO', + 'co-applicant-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new CoBorrowerEmploymentInfoPage('PAGETITLES.COMMON.COAPP_EMPLOYMENT_INFO', + 'co-applicant-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* CO_APPLICANT_SPOUSE */ + new SpouseTypeInfoPage('PAGETITLES.COMMON.COAPP_SPOUSETYPE', + 'co-applicant-spouse-type', + [ + CommonGroups.IS_FULL_INFO_SPOUSE + ]), + new CoBorrowerTattleSpouseInfoPage('PAGETITLES.COMMON.COAPP_TATTLESPOUSE', + 'co-applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO + ]), + new CoBorrowerSpousePersonalInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_PERSONAL_INFO', + 'co-applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new CoBorrowerSpouseAddressInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_ADDRESS_INFO', + 'co-applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new CoBorrowerSpouseEmploymentInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_EMPLOYMENT_INFO', + 'co-applicant-spouse-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* DISCLOSURE / SUMMARY */ + new CreditCardDisclosuresInfoPage('PAGETITLES.COMMON.DISCLOSURES', + 'cc-disclosures', + [ + CommonGroups.CC_DISCLOSURE_INFO + ]), + new SummaryInfoPage('PAGETITLES.COMMON.SUMMARY', 'cc-summary', []), + new ProcessingPage('PAGETITLES.CC.PROCESS', 'process', []), + new DecisionPage('PAGETITLES.COMMON.DECISION', 'decision', []) +]; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-routing.module.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-routing.module.ts new file mode 100644 index 0000000..e47c3f7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-routing.module.ts @@ -0,0 +1,64 @@ +import { Route} from '@angular/router'; +import { MemberInfoPageComponent } from '../common/member/member-info-page.component'; +import { PersonalInfoPageComponent } from '../common/personal/personal-info-page.component'; +import { AddressInfoPageComponent } from '../common/address/address-info-page.component'; +import { EmploymentInfoPageComponent } from '../common/employment/employment-info-page.component'; +import { CreditCardDisclosuresPageComponent } from './credit-card-disclosures-page.component'; +import { SummaryPageComponent } from '../common/summary/summary-page.component'; +import { DecisionPageComponent } from '../common/decision/decision-page.component'; +import { LoanRouteGuard } from '../../../shared/guards/loan-route.guard'; +import { BorrowerType } from '../../../shared/borrower-type'; +import { TattleSpouseInfoPageComponent } from '../common/tattle-spouse/tattle-spouse-info-page.component'; +import { SpouseTypeInfoPageComponent } from '../common/spouse-type/spouse-type-info-page.component'; +import { ProcessingPageComponent } from '../common/processing/processing-page.component'; +import { StepperControllerGuard } from 'src/app/shared/guards/stepper-controller-guard'; +import { MemberCoreResolver } from 'src/app/member/member-core.resolver'; +import { environment } from 'src/environments/environment'; + + +export const ccRoutes: Route = { + path: 'cc/:type', + resolve: environment.coreResolver ? { CoreIndividual: MemberCoreResolver} : {}, + canActivate: [StepperControllerGuard], + canActivateChild: [LoanRouteGuard], + children: [ + { path: 'step-1', component: MemberInfoPageComponent }, + { path: 'step-2', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'step-3', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'step-4', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'applicant-spouse-type', component: SpouseTypeInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'applicant-spouse', component: TattleSpouseInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'co-applicant-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-spouse-type', component: SpouseTypeInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-spouse', component: TattleSpouseInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'cc-disclosures', component: CreditCardDisclosuresPageComponent }, + { path: 'cc-summary', component: SummaryPageComponent }, + { path: 'process', component: ProcessingPageComponent }, + { path: 'decision', component: DecisionPageComponent }, + { path: '', redirectTo: 'step-1', pathMatch: 'prefix' } + ] +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.html new file mode 100644 index 0000000..586597e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.html @@ -0,0 +1 @@ + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.spec.ts new file mode 100644 index 0000000..2838a9c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.spec.ts @@ -0,0 +1,49 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditCardSchumerBoxComponent } from './credit-card-schumer-box.component'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +describe('CreditCardSchumerBoxComponent', () => { + let component: CreditCardSchumerBoxComponent; + let fixture: ComponentFixture; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + CreditCardSchumerBoxComponent + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + fixture = TestBed.createComponent(CreditCardSchumerBoxComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.ts new file mode 100644 index 0000000..6412501 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/credit-card/credit-card-schumer-box.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from '@angular/core'; +import { ApplicationWizardService } from '../../application-wizard.service'; + +@Component({ + selector: 'lw-credit-card-schumer-box', + templateUrl: './credit-card-schumer-box.component.html' +}) +export class CreditCardSchumerBoxComponent implements OnInit { + + public localState = { + boxType: null, + showSchumer: false + }; + + constructor( + private applicationWizardService: ApplicationWizardService + ) { } + + ngOnInit() { + this.localState.boxType = this.applicationWizardService.getFieldValue('loanType').apiCode; + this.localState.showSchumer = this.applicationWizardService.getFieldValue('loanType').category === 'Credit Card'; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.html new file mode 100644 index 0000000..931ce8e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.html @@ -0,0 +1,43 @@ +
+
+
+

+
+
COPY.COLLATERAL.PRIMARY_BORROWER
+
+
+ + + + + + + + + + + +
+ +
+ + +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.spec.ts new file mode 100644 index 0000000..141684b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.spec.ts @@ -0,0 +1,457 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { AddressInfoFormComponent } from '../../field-groups/address/address-info-form.component'; +import { IsAddressSameInfoFormComponent } from '../../field-groups/address/is-address-same-info-form.component'; +import { CollateralInfoPageComponent } from './collateral-info-page.component'; +import { CollateralInfoPageService } from './collateral-info-page.service'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { CreditApplicationValues } from '../../../shared/credit-application-values'; +import { StatePipe } from '../../../shared/pipes/state.pipe'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { UiSettings } from '../../../shared/configuration/ui-settings'; +import { DefaultSettings } from '../../../shared/configuration/default-settings.mock'; +import { ConfigurationService } from '../../../shared/configuration/configuration.service'; +import { CollateralInformationGroup} from '../../../shared/credit-application'; +import { configureTestSuite } from 'ng-bullet'; +import { AddressInformation } from '../../field-groups/address/address-information'; +import { BorrowerType } from '../../../shared/borrower-type'; + +class ApplicationWizardServiceMock { + private readonly loanType = LoanTypeSet.find((loan) => loan.id === 'hef') || LoanTypeSet[0]; + + private readonly borrowerAddress: AddressInformation = { + addressLine1: '123 Mockingbird Ln', + addressLine2: '', + city: 'Madison', + state: 'WI', + zipcode: '53703' + }; + + private readonly collateralInformation: CollateralInformationGroup = { + estimatedValue: 0, + propertyType: '', + occupancyType: '', + hasFirstMortgage: CreditApplicationValues.CHOICE_NO, + hasSecondMortgage: CreditApplicationValues.CHOICE_NO, + payoffFirstMortgage: false, + payoffSecondMortgage: false, + isBeingImproved: CreditApplicationValues.CHOICE_NO, + isListedForSale: CreditApplicationValues.CHOICE_NO, + addressLine1: '', + addressLine2: '', + city: '', + state: '', + zipcode: '', + isAddressSame: CreditApplicationValues.CHOICE_YES, + onRentedLot: CreditApplicationValues.CHOICE_NO, + rentPaid: '', + annualPropertyTax: '', + files: [], + firstMortgageBalanceFiles: [], + isUploadingFirstMortgageBalanceFiles: false, + isUploading: false + }; + + private readonly fullCreditApplication: any = { + loanId: 'eq-collateral', + loanType: this.loanType, + collateralInformation: this.collateralInformation, + hasCoApplicant: CreditApplicationValues.CHOICE_NO, + hasCoApplicantSpouse: CreditApplicationValues.CHOICE_NO, + hasBeenCompleted: false, + limitRequested: 0, + primaryContactMethod: 'email', + fullInfoBorrowers: [{ + personalInformation: { firstName: 'Alex', maritalStatus: CreditApplicationValues.CHOICE_NO }, + addressInformation: this.borrowerAddress, + residenceInformation: {} + }] + }; + + public getNextTopicMessage(): string { + return 'Next topic message'; + } + + public getNextRoute(): string { + return '../cc-disclosures'; + } + + public getPreviousRoute(): string { + return '../co-applicant-spouse-financial'; + } + + public getFieldValue(field: string, group?: string): any { + if (group) { + const groupedValues = this.fullCreditApplication[group]; + return groupedValues ? groupedValues[field] : null; + } + return this.fullCreditApplication[field] || null; + } + + public getGroupValues(group: string): any { + return this.fullCreditApplication[group] || {}; + } + + public getFullCreditApplication(): any { + return this.fullCreditApplication; + } + + public getIndividualGroupValues(group: string, borrowerType: BorrowerType): any { + const individual = this.getIndividualByBorrowerType(borrowerType); + if (individual && individual[group]) { + return individual[group]; + } + return {}; + } + + public getIndividualByBorrowerType(borrowerType: BorrowerType): any { + return this.fullCreditApplication.fullInfoBorrowers[0]; + } + + public getApplicantName(): string { + return this.fullCreditApplication.fullInfoBorrowers[0].personalInformation.firstName; + } + + public isRentedLotInfoRequired(): boolean { + return true; + } + + public isLienPositionInfoRequired(): boolean { + return true; + } + + public needsCollateral(): boolean { + return true; + } +} + + +describe('CollateralInfoPageComponent', () => { + let component: CollateralInfoPageComponent; + let fixture: ComponentFixture; + let service: CollateralInfoPageService; + let configService: ConfigurationService; + let applicationWizardService: ApplicationWizardService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + CollateralInfoPageComponent, + IsAddressSameInfoFormComponent, + AddressInfoFormComponent, + StatePipe + ], + providers: [ + ConfigurationService, + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService}, + {provide: ApplicationWizardService, useClass: ApplicationWizardServiceMock} + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + fixture = TestBed.createComponent(CollateralInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(CollateralInfoPageService); + configService = TestBed.get(ConfigurationService); + configService.saveConfiguration(defaultSettings); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.pageForm).toBeTruthy(); + expect(component.collateralInfoGroup).toBeTruthy(); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + expect(component.commandPrevious).not.toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + fixture.detectChanges(); + spyOn(service, 'saveAndNavigate'); + component.localState.nextDisabled = false; + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + spyOn(service, 'cancelAndNavigate'); + component.commandPrevious(); + expect(service.cancelAndNavigate).toHaveBeenCalled(); + }); + + it('should bind the previous button to the correct command function', async () => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonPrevious')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).not.toHaveBeenCalled(); + expect(component.commandPrevious).toHaveBeenCalled(); + }); + }); + + it('should update on new values', () => { + fixture.detectChanges(); + spyOn(component, 'updateCollateralFormGroupControls').and.callThrough(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_NO}); + fixture.detectChanges(); + expect(component.updateCollateralFormGroupControls).toHaveBeenCalled(); + }); + + it('should use the service to add address controls to the form group', () => { + spyOn(service, 'addAddressGroupsIfMissing').and.callThrough(); + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_NO}); + fixture.detectChanges(); + expect(service.addAddressGroupsIfMissing).toHaveBeenCalled(); + expect(component.addressInfoGroup.value.addressLine1).toBeDefined(); + }); + + it('should use the service to remove address controls from the form group', () => { + fixture.detectChanges(); + spyOn(service, 'addAddressGroupsIfMissing').and.callThrough(); + spyOn(service, 'removeAddressGroups').and.callThrough(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_NO}); + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_YES}); + fixture.detectChanges(); + expect(service.removeAddressGroups).toHaveBeenCalled(); + expect(component.addressInfoGroup).toBeFalsy(); + }); + + it('should be require rent paid if the Property is a manufactured home', () => { + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({ + isAddressSame: CreditApplicationValues.CHOICE_YES, + estimatedValue: 10000, + propertyType: 'Double', + isBeingImproved: CreditApplicationValues.CHOICE_NO, + isListedForSale: CreditApplicationValues.CHOICE_YES, + occupancyType: 'Vacant', + onRentedLot: CreditApplicationValues.CHOICE_YES + }); + fixture.detectChanges(); + expect(component.collateralInfoGroup.valid).toBe(false); + component.collateralInfoGroup.patchValue({ + rentPaid: 350 + }); + fixture.detectChanges(); + expect(component.collateralInfoGroup.valid).toBe(true); + }); + + it('should patch page data onInit', () => { + const collateralData: CollateralInformationGroup = { + estimatedValue: 5446, + propertyType: 'Condo', + occupancyType: 'OwnerOccupied', + hasFirstMortgage: CreditApplicationValues.CHOICE_YES, + hasSecondMortgage: CreditApplicationValues.CHOICE_YES, + payoffFirstMortgage: false, + payoffSecondMortgage: false, + isBeingImproved: CreditApplicationValues.CHOICE_YES, + isListedForSale: CreditApplicationValues.CHOICE_NO, + addressLine1: '51 Gardner', + addressLine2: '', + city: 'Alston', + state: 'MA', + zipcode: '54545', + isAddressSame: CreditApplicationValues.CHOICE_NO, + onRentedLot: '', + rentPaid: '', + annualPropertyTax: '5000.00', + files: [], + firstMortgageBalanceFiles: [], + isUploadingFirstMortgageBalanceFiles: false, + isUploading: false + }; + + spyOn(service, 'getApplicationGroupValues').and.returnValue(collateralData); + fixture.componentInstance.ngOnInit(); + expect(Object.assign(component.collateralInfoGroup.value, component.addressInfoGroup.value)).toEqual(collateralData); + }); + + it('should submit the USPS Verified Address Value if the match was saved', () => { + spyOn(service, 'saveAndNavigate'); + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_NO}); + + const mockAddressInput: AddressInformation = { + addressLine1: '51 Gardner', + addressLine2: '', + city: 'Alston', + state: 'MA', + zipcode: '54545', + }; + const mockUSPSAddress: AddressInformation = { + addressLine1: mockAddressInput.addressLine1.toLocaleUpperCase(), + addressLine2: mockAddressInput.addressLine2.toLocaleUpperCase(), + city: mockAddressInput.city.toLocaleUpperCase(), + state: mockAddressInput.state.toLocaleUpperCase(), + zipcode: mockAddressInput.zipcode, + zipcodeExt: '5446' + }; + + component.addressInfoGroup.patchValue(mockAddressInput); + component.confirmRealEstateAddressInformation(mockUSPSAddress); + const fields = component.generateFieldsFromGroups(); + expect(fields[CollateralInfoPageService.ADDRESS_FORM_NAME]).toEqual(mockUSPSAddress); + }); + + it('Should Require WI collateral for address info if is HELOC Fixed Or Flex', () => { + spyOn(service, 'saveAndNavigate'); + spyOn(service, 'isHelocOrHomeEquityFixedLoan').and.returnValue(true); + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_NO}); + const mockAddressInput: AddressInformation = { + addressLine1: '51 Gardner', + addressLine2: '', + city: 'Washington', + state: 'DC', + zipcode: '25000', + }; + + component.addressInfoGroup.patchValue(mockAddressInput); + fixture.detectChanges(); + const stateStatus = component.addressInfoGroup.get('state').status; + const addresSameStatus = component.collateralInfoGroup.get('isAddressSame').status; + expect(addresSameStatus).toBe('INVALID'); + expect(stateStatus).toBe('INVALID'); + }); + + it('Should Not Display Error Text for WI collateral if loan is HELOC Fixed Or Flex and AddressInfo is Wisconsin', () => { + spyOn(service, 'saveAndNavigate'); + spyOn(service, 'isHelocOrHomeEquityFixedLoan').and.returnValue(true); + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_NO}); + const mockAddressInput: AddressInformation = { + addressLine1: '51 Gardner', + addressLine2: '', + city: 'Milwaukee', + state: 'WI', + zipcode: '53211', + }; + + component.addressInfoGroup.patchValue(mockAddressInput); + fixture.detectChanges(); + const stateStatus = component.addressInfoGroup.get('state').status; + const addresSameStatus = component.collateralInfoGroup.get('isAddressSame').status; + expect(addresSameStatus).toBe('VALID'); + expect(stateStatus).toBe('VALID'); + }); + + it('Should be invalid if Addres Same NO if Primary Holder Address is not in WI and loan is HELOC fix or flex ', () => { + spyOn(applicationWizardService, 'getIndividualGroupValues').and.callFake((arg1, arg2) => { + if (arg1 === 'addressInformation' && arg2 === 'PRIMARY_BORROWER') { + return {state: 'DC'}; + } + }); + spyOn(service, 'saveAndNavigate'); + spyOn(service, 'isHelocOrHomeEquityFixedLoan').and.returnValue(true); + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_NO}); + const mockAddressInput: AddressInformation = { + addressLine1: '51 Gardner', + addressLine2: '', + city: 'Washington', + state: 'DC', + zipcode: '25000', + }; + + component.addressInfoGroup.patchValue(mockAddressInput); + fixture.detectChanges(); + const stateStatus = component.addressInfoGroup.get('state').status; + const addresSameStatus = component.collateralInfoGroup.get('isAddressSame').status; + expect(addresSameStatus).toBe('INVALID'); + expect(stateStatus).toBe('INVALID'); + }); + + it('Shound be valid if addres same YES if Primary Holder Address is in WI and Loan is HELOC fix or flex', () => { + spyOn(applicationWizardService, 'getIndividualGroupValues').and.callFake((arg1, arg2) => { + if (arg1 === 'addressInformation' && arg2 === 'PRIMARY_BORROWER') { + return {state: 'WI'}; + } + }); + spyOn(service, 'saveAndNavigate'); + spyOn(service, 'isHelocOrHomeEquityFixedLoan').and.returnValue(true); + + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_YES}); + fixture.detectChanges(); + + const addresSameStatus = component.collateralInfoGroup.get('isAddressSame').status; + expect(addresSameStatus).toBe('VALID'); + }); + + it('Should be valid if Addres Same NO if Primary Holder Address is not in WI when loan is not HELOC fix or flex', () => { + spyOn(applicationWizardService, 'getIndividualGroupValues').and.callFake((arg1, arg2) => { + if (arg1 === 'addressInformation' && arg2 === 'PRIMARY_BORROWER') { + return {state: 'DC'}; + } + }); + spyOn(service, 'saveAndNavigate'); + spyOn(service, 'isHelocOrHomeEquityFixedLoan').and.returnValue(false); + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_NO}); + const mockAddressInput: AddressInformation = { + addressLine1: '51 Gardner', + addressLine2: '', + city: 'Washington', + state: 'DC', + zipcode: '25000', + }; + + component.addressInfoGroup.patchValue(mockAddressInput); + fixture.detectChanges(); + const stateStatus = component.addressInfoGroup.get('state').status; + const addresSameStatus = component.collateralInfoGroup.get('isAddressSame').status; + expect(addresSameStatus).toBe('VALID'); + expect(stateStatus).toBe('VALID'); + }); + + it(' Shound be valid if addres same YES and Primary Holder Address is not in WI when loan is not Heloc Fixed or Flex', () => { + spyOn(applicationWizardService, 'getIndividualGroupValues').and.callFake((arg1, arg2) => { + if (arg1 === 'addressInformation' && arg2 === 'PRIMARY_BORROWER') { + return {state: 'DC'}; + } + }); + spyOn(service, 'saveAndNavigate'); + spyOn(service, 'isHelocOrHomeEquityFixedLoan').and.returnValue(false); + + fixture.detectChanges(); + component.collateralInfoGroup.patchValue({isAddressSame: CreditApplicationValues.CHOICE_YES}); + fixture.detectChanges(); + + const addresSameStatus = component.collateralInfoGroup.get('isAddressSame').status; + expect(addresSameStatus).toBe('VALID'); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.ts new file mode 100644 index 0000000..3da1566 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.component.ts @@ -0,0 +1,211 @@ +import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { CollateralInfoPageService } from './collateral-info-page.service'; +import { Subject, Subscription } from 'rxjs'; +import { CreditApplicationValues } from '../../../shared/credit-application-values'; +import { AddressInformation } from '../../field-groups/address/address-information'; +import { AddressFormBuilderService } from 'src/app/shared/address-validation/address-form-builder.service'; +import { AddressDiffInformation } from '../../field-groups/address-compare/address-compare-information'; +import { AddressValidationFormatConverter } from 'src/app/shared/address-validation/address-validation-format-converter'; +import { OccupancyTypeEnum } from 'src/app/shared/api-models/api-enums'; + +@Component({ + selector: 'lw-collateral-info-page', + templateUrl: './collateral-info-page.component.html' +}) +export class CollateralInfoPageComponent implements OnInit, OnDestroy { + + private statusSubscription: Subscription; + private valueSubscription: Subscription; + + public pageForm: FormGroup; + public collateralInfoGroup: FormGroup; + public addressInfoGroup: FormGroup; + public realEstateAddressVerifyInfoGroup: FormGroup; + + private readonly FORM_STATE_INVALID = 'INVALID'; + + public savingData = null; + + public loanId: string; + + public fileUploadValues: any; + + public localState = { + nextDisabled: true, + previousDisabled: false, + nextTopic: '', + sameAddress: true, + isAddressSameType: 'heloc', + askAboutRentedLot: false, + borrowerName: 'Applicant', + realEstateAddressInformation: null, + isHelocRateMod: false, + isMfgHome: false + }; + + constructor( + private collateralInfoPageService: CollateralInfoPageService, + private route: ActivatedRoute, + private cd: ChangeDetectorRef) { + } + + public ngOnInit() { + this.loanId = this.collateralInfoPageService.getLoanId(); + this.fileUploadValues = this.collateralInfoPageService.getFileUploadValues(); + + this.localState.borrowerName = this.collateralInfoPageService.getApplicantName(); + this.pageForm = this.collateralInfoPageService.createPageGroup(); + this.collateralInfoGroup = this.pageForm.get(CollateralInfoPageService.COLLATERAL_FORM_NAME); + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + this.valueSubscription = this.pageForm.valueChanges.subscribe((change) => { + const collateralInfo = change.collateralInformation; + this.updateCollateralFormGroupControls(collateralInfo); + }); + this.localState.nextTopic = this.collateralInfoPageService.getNextTopicMessage(); + this.collateralInfoGroup + .patchValue( + this.collateralInfoPageService + .getApplicationGroupValues(CollateralInfoPageService.COLLATERAL_FORM_NAME) + ); + if (this.collateralInfoGroup.value.isAddressSame === CreditApplicationValues.CHOICE_NO) { + this.updateCollateralFormGroupControls(this.collateralInfoGroup.value); + const previousValues = this.collateralInfoPageService.getApplicationGroupValues(CollateralInfoPageService.COLLATERAL_FORM_NAME); + + if (previousValues.state) { + this.addressInfoGroup.patchValue(previousValues); + this.localState.realEstateAddressInformation = AddressValidationFormatConverter.filterUiAddressInformation(previousValues); + } + } + + this.localState.askAboutRentedLot = this.collateralInfoPageService.isRentedLotInfoRequired(); + if (!this.localState.askAboutRentedLot) { + this.collateralInfoGroup = this.collateralInfoPageService.clearRentInfoRequirement(this.collateralInfoGroup); + } + + this.localState.isHelocRateMod = this.collateralInfoPageService.isHelocRateMod(); + this.localState.isMfgHome = this.collateralInfoPageService.isMfgHome(); + } + + public ngOnDestroy() { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public updateCollateralFormGroupControls(change) { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + if (!change) { + return; + } + + if (change.isAddressSame === CreditApplicationValues.CHOICE_NO) { + this.localState.sameAddress = false; + this.collateralInfoPageService.addAddressGroupsIfMissing(this.pageForm); + this.addressInfoGroup = this.pageForm.get(CollateralInfoPageService.ADDRESS_FORM_NAME); + } else if (change.isAddressSame === CreditApplicationValues.CHOICE_YES) { + this.localState.sameAddress = true; + this.collateralInfoPageService.removeAddressGroups(this.pageForm); + this.addressInfoGroup = null; + this.realEstateAddressVerifyInfoGroup = null; + this.collateralInfoGroup.patchValue({occupancyType: OccupancyTypeEnum[0]}); + } + + if (change.hasFirstMortgage === CreditApplicationValues.CHOICE_NO) { + const nextCollateralInfoGroup = this.collateralInfoPageService.removeFirstMortgagePayoff(this.collateralInfoGroup); + this.collateralInfoGroup = nextCollateralInfoGroup; + this.collateralInfoGroup.patchValue({payoffFirstMortgage: 'false'}); + } else if (change.hasFirstMortgage === CreditApplicationValues.CHOICE_YES) { + const nextCollateralInfoGroup = this.collateralInfoPageService.addFirstMortgagePayoff(this.collateralInfoGroup); + this.collateralInfoGroup = nextCollateralInfoGroup; + } + + if (change.hasSecondMortgage === CreditApplicationValues.CHOICE_NO) { + const nextCollateralInfoGroup = this.collateralInfoPageService.removeSecondMortgagePayoff(this.collateralInfoGroup); + this.collateralInfoGroup = nextCollateralInfoGroup; + this.collateralInfoGroup.patchValue({payoffSecondMortgage: 'false'}); + } else if (change.hasSecondMortgage === CreditApplicationValues.CHOICE_YES) { + const nextCollateralInfoGroup = this.collateralInfoPageService.addSecondMortgagePayoff(this.collateralInfoGroup); + this.collateralInfoGroup = nextCollateralInfoGroup; + } + + if (this.localState.isHelocRateMod) { + const nextCollateralInfoGroup = this.collateralInfoPageService.removeSecondMortgagePayoff(this.collateralInfoGroup); + this.collateralInfoGroup = nextCollateralInfoGroup; + this.collateralInfoGroup.patchValue({hasFirstMortgage: 'true'}); + this.collateralInfoGroup.patchValue({payoffFirstMortgage: 'false'}); + this.collateralInfoGroup.patchValue({hasSecondMortgage: 'false'}); + this.collateralInfoGroup.patchValue({payoffSecondMortgage: 'false'}); + this.collateralInfoGroup.patchValue({isBeingImproved: 'false'}); + } + + this.valueSubscription = this.pageForm.valueChanges.subscribe((nextValues) => { + const collateralInfo = nextValues.collateralInformation; + this.updateCollateralFormGroupControls(collateralInfo); + }); + this.cd.detectChanges(); + } + + public confirmRealEstateAddressInformation(addressInformation: AddressInformation) { + if (!this.realEstateAddressVerifyInfoGroup) { + AddressFormBuilderService.addAddressCompareGroupIfMissing( + this.pageForm, CollateralInfoPageService.REAL_ESTATE_ADDRESS_COMPARE_FORM_NAME + ); + this.realEstateAddressVerifyInfoGroup = this.pageForm.get(CollateralInfoPageService.REAL_ESTATE_ADDRESS_COMPARE_FORM_NAME); + } + if (!addressInformation) { + this.realEstateAddressVerifyInfoGroup.patchValue({addressVerifyStatus: false}); + } else { + this.realEstateAddressVerifyInfoGroup.patchValue({addressVerifyStatus: true}); + const addressDiff: AddressDiffInformation = AddressValidationFormatConverter.getAddressValidationDiff( + AddressValidationFormatConverter.makeApiAddress(this.addressInfoGroup.value), + addressInformation + ); + if (!addressDiff.allFieldsSame) { + this.addressInfoGroup.patchValue(addressInformation); + } + } + this.localState.realEstateAddressInformation = addressInformation; + } + + public getAddressValues(): AddressInformation | null { + if (this.localState.realEstateAddressInformation) { + return this.localState.realEstateAddressInformation; + } else if (this.addressInfoGroup) { + return this.addressInfoGroup.value; + } else { + return null; + } + } + + public generateFieldsFromGroups() { + const fields = Object.assign({}, {[CollateralInfoPageService.COLLATERAL_FORM_NAME]: this.collateralInfoGroup.value}); + const address = this.getAddressValues(); + if (address) { + Object.assign(fields, {[CollateralInfoPageService.ADDRESS_FORM_NAME]: address}); + } + return fields; + } + + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + this.collateralInfoPageService.saveAndNavigate(this.generateFieldsFromGroups(), this.route); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + } + + public commandPrevious() { + this.collateralInfoPageService.cancelAndNavigate(this.route); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.service.spec.ts new file mode 100644 index 0000000..87f2e2d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.service.spec.ts @@ -0,0 +1,178 @@ +import { ApplicationWizardService } from '../../application-wizard.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule, FormGroup } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { AddressInformation } from '../../field-groups/address/address-information'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { IsAddressSameInformation } from '../../field-groups/address/is-address-same-information'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { CollateralInfoPageService } from './collateral-info-page.service'; +import { CollateralInformation } from '../../field-groups/equity/collateral-information'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { AddressFormBuilderService } from 'src/app/shared/address-validation/address-form-builder.service'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; + + +describe('Collateral Info Page Service', () => { + + let service: CollateralInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(CollateralInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[5].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + creditApplicationService = TestBed.get(CreditApplicationService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create the top level form group', () => { + spyOn(service, 'createCollateralInformationGroup').and.callThrough(); + service.createPageGroup(); + expect(service.createCollateralInformationGroup).toHaveBeenCalled(); + }); + + it('should create a collateral info group', () => { + const collateralGroup = service.createCollateralInformationGroup(); + expect(collateralGroup).toBeTruthy(); + CommonGroups.IS_ADDRESS_SAME_INFO.fields.forEach((eachField) => { + expect(collateralGroup.get(eachField.field)).toBeTruthy(); + }, this); + CommonGroups.COLLATERAL_INFO.fields.forEach((eachField) => { + expect(collateralGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should remove addressInfoGroup to the page form', () => { + const formGroup = new FormGroup({}); + service.addAddressGroupsIfMissing(formGroup); + AddressFormBuilderService.addAddressCompareGroupIfMissing(formGroup, CollateralInfoPageService.REAL_ESTATE_ADDRESS_COMPARE_FORM_NAME); + expect(formGroup.get(CollateralInfoPageService.ADDRESS_FORM_NAME)).toBeTruthy(); + expect(formGroup.get(CollateralInfoPageService.REAL_ESTATE_ADDRESS_COMPARE_FORM_NAME)).toBeTruthy(); + service.removeAddressGroups(formGroup); + expect(formGroup.get(CollateralInfoPageService.ADDRESS_FORM_NAME)).toBeFalsy(); + expect(formGroup.get(CollateralInfoPageService.REAL_ESTATE_ADDRESS_COMPARE_FORM_NAME)).toBeFalsy(); + }); + + it('should add first mortgage payoff control to the form group', () => { + const collateralGroup = service.createCollateralInformationGroup(); + expect(collateralGroup).toBeTruthy(); + const collateralGroupWithoutFirstMortgagePayoff = service.removeFirstMortgagePayoff(collateralGroup); + CommonGroups.LIEN_POSITION_INFO_FIRST_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + expect(collateralGroupWithoutFirstMortgagePayoff.get(eachField.field)).toBeFalsy(); + }, this); + + const collateralGroupWithFirstMortgagePayoff = service.addFirstMortgagePayoff(collateralGroupWithoutFirstMortgagePayoff); + CommonGroups.LIEN_POSITION_INFO_FIRST_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + expect(collateralGroupWithFirstMortgagePayoff.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should remove first mortgage payoff control from the form group', () => { + const collateralGroup = service.createCollateralInformationGroup(); + const collateralGroupWithFirstMortgagePayoff = service.addFirstMortgagePayoff(collateralGroup); + CommonGroups.LIEN_POSITION_INFO_FIRST_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + expect(collateralGroupWithFirstMortgagePayoff.get(eachField.field)).toBeTruthy(); + }, this); + + const collateralGroupWithoutFirstMortgagePayoff = service.removeFirstMortgagePayoff(collateralGroupWithFirstMortgagePayoff); + CommonGroups.LIEN_POSITION_INFO_FIRST_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + expect(collateralGroupWithoutFirstMortgagePayoff.get(eachField.field)).toBeFalsy(); + }, this); + }); + + it('should add second mortgage payoff control to the form group', () => { + const collateralGroup = service.createCollateralInformationGroup(); + expect(collateralGroup).toBeTruthy(); + const collateralGroupWithoutSecondMortgagePayoff = service.removeSecondMortgagePayoff(collateralGroup); + CommonGroups.LIEN_POSITION_INFO_SECOND_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + expect(collateralGroupWithoutSecondMortgagePayoff.get(eachField.field)).toBeFalsy(); + }, this); + + const collateralGroupWithSecondMortgagePayoff = service.addSecondMortgagePayoff(collateralGroupWithoutSecondMortgagePayoff); + CommonGroups.LIEN_POSITION_INFO_SECOND_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + expect(collateralGroupWithSecondMortgagePayoff.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should remove second mortgage payoff control from the form group', () => { + const collateralGroup = service.createCollateralInformationGroup(); + const collateralGroupWithSecondMortgagePayoff = service.addSecondMortgagePayoff(collateralGroup); + CommonGroups.LIEN_POSITION_INFO_SECOND_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + expect(collateralGroupWithSecondMortgagePayoff.get(eachField.field)).toBeTruthy(); + }, this); + + const collateralGroupWithoutSecondMortgagePayoff = service.removeSecondMortgagePayoff(collateralGroupWithSecondMortgagePayoff); + CommonGroups.LIEN_POSITION_INFO_SECOND_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + expect(collateralGroupWithoutSecondMortgagePayoff.get(eachField.field)).toBeFalsy(); + }, this); + }); + + it('should provide the value for the groups', () => { + const testGroupName = 'testGroup'; + spyOn(applicationWizardService, 'getGroupValues').and.returnValue(true); + service.getApplicationGroupValues(testGroupName); + expect(applicationWizardService.getGroupValues).toHaveBeenCalledWith(testGroupName); + }); + + it('should emit an error from the "Estimated Value" control if it is out of range', () => { + const testCollateralGroup = service.createCollateralInformationGroup(); + testCollateralGroup.patchValue({estimatedValue: 1000000000}); + expect(testCollateralGroup.controls.estimatedValue.errors.maxCurrency).toBeTruthy(); + }); + + it('should navigate the application, on successful save', () => { + spyOn(creditApplicationService, 'saveCollateralInformation').and.returnValue(of(true)); + spyOn(router, 'navigate'); + const info = <[IsAddressSameInformation, AddressInformation, CollateralInformation]>{}; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'saveCollateralInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + const info = <[IsAddressSameInformation, AddressInformation, CollateralInformation]>{}; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('cancelling should always navigate', () => { + spyOn(router, 'navigate'); + const activatedRoute = {}; + service.cancelAndNavigate(activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.service.ts new file mode 100644 index 0000000..e13324f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.service.ts @@ -0,0 +1,261 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormBuilder, FormGroup, ValidatorFn } from '@angular/forms'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { PageService } from '../page-service'; +import { BorrowerType } from '../../../shared/borrower-type'; +import { LoanType } from '../../../shared/loan-type'; +import { CreditApplicationValues } from 'src/app/shared/credit-application-values'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; +import { FormatService } from 'src/app/shared/pipes/format.service'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import {LoanTitle} from '../../../shared/loan-type/loan-title'; + +@Injectable({ + providedIn: 'root' +}) +export class CollateralInfoPageService implements PageService { + + public static readonly COLLATERAL_FORM_NAME = 'collateralInformation'; + public static readonly ADDRESS_FORM_NAME = 'addressInformation'; + public static readonly REAL_ESTATE_ADDRESS_COMPARE_FORM_NAME = 'realEstateCollateralInformation'; + private readonly CURRENT_ROUTE = 'eq-collateral'; + private readonly REQUIRED_STATE = 'WI'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private formatService: FormatService, + private router: Router, + private formBuilder: FormBuilder) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + + const collateralGroup = this.createCollateralInformationGroup(); + group.addControl(CollateralInfoPageService.COLLATERAL_FORM_NAME, collateralGroup); + + return group; + } + + public createCollateralInformationGroup(): FormGroup { + const collateralGroup = this.formBuilder.group({}); + const loanType: LoanType = this.applicationWizardService.getGroupValues('loanType'); + const primaryBorrowerAddress = + this.applicationWizardService.getIndividualGroupValues('addressInformation', BorrowerType.PRIMARY_BORROWER).state; + + + CommonGroups.IS_ADDRESS_SAME_INFO.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + validators.push(validationFunction(this.isHelocOrHomeEquityFixedLoan(), primaryBorrowerAddress)); + }, this); + } + collateralGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + + CommonGroups.COLLATERAL_INFO.fields.forEach((eachField) => { + const validators: Array = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + const validatorFn = validationFunction(loanType); + validators.push(validatorFn); + }, this); + } + collateralGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + + CommonGroups.LIEN_POSITION_INFO.fields.forEach((eachField) => { + const validators: Array = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + const validatorFn = validationFunction(loanType); + validators.push(validatorFn); + }, this); + } + collateralGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + + return collateralGroup; + } + + public isHelocOrHomeEquityFixedLoan(): boolean { + return this.applicationWizardService.getGroupValues('loanType').apiCode === LoanCode.EQ_HELOC_Fixed || + this.applicationWizardService.getGroupValues('loanType').apiCode === LoanCode.EQ_HELOC_Flex; + } + + public createAddressInformationGroup(): FormGroup { + const memberGroup = this.formBuilder.group({}); + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + validators.push(validationFunction(this.isHelocOrHomeEquityFixedLoan(), this.REQUIRED_STATE)); + }, this); + } + memberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + + return memberGroup; + } + + public addAddressGroupsIfMissing(group: FormGroup) { + if (group.get(CollateralInfoPageService.ADDRESS_FORM_NAME)) { + return; + } + const addressGroup = this.createAddressInformationGroup(); + + group.addControl(CollateralInfoPageService.ADDRESS_FORM_NAME, addressGroup); + } + + public removeAddressGroups(group: FormGroup) { + if (!group.get(CollateralInfoPageService.ADDRESS_FORM_NAME)) { + return; + } + group.removeControl(CollateralInfoPageService.ADDRESS_FORM_NAME); + group.removeControl(CollateralInfoPageService.REAL_ESTATE_ADDRESS_COMPARE_FORM_NAME); + } + + public addFirstMortgagePayoff(group: FormGroup): FormGroup { + CommonGroups.LIEN_POSITION_INFO_FIRST_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + group.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }); + + return group; + } + + public removeFirstMortgagePayoff(group: FormGroup): FormGroup { + CommonGroups.LIEN_POSITION_INFO_FIRST_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + group.removeControl(eachField.field); + }); + + return group; + } + + public addSecondMortgagePayoff(group: FormGroup): FormGroup { + CommonGroups.LIEN_POSITION_INFO_SECOND_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + group.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }); + + return group; + } + + public removeSecondMortgagePayoff(group: FormGroup): FormGroup { + CommonGroups.LIEN_POSITION_INFO_SECOND_MORTGAGE_PAYOFF.fields.forEach((eachField) => { + group.removeControl(eachField.field); + }); + + return group; + } + + public getApplicationGroupValues(groupName: string): any { + return this.applicationWizardService.getGroupValues(groupName); + } + + public saveAndNavigate(values, route: ActivatedRoute) { + this.creditApplicationService.saveCollateralInformation(values).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + console.log(failure); + // notify user, server, log, etc. + } + ); + } + + public cancelAndNavigate(route: ActivatedRoute) { + const prevRoute = this.applicationWizardService.getPreviousRoute(this.CURRENT_ROUTE); + this.router.navigate([prevRoute], {relativeTo: route}); + } + + public getNextTopicMessage(): string { + return this.applicationWizardService.getNextTopicMessage(this.CURRENT_ROUTE); + } + + public isRentedLotInfoRequired(): boolean { + return this.applicationWizardService.isRentedLotInfoRequired(); + } + + public isLienPositionInfoRequired(): boolean { + return this.applicationWizardService.isLienPositionInfoRequired(); + } + + public clearRentInfoRequirement(group: FormGroup): FormGroup { + group.removeControl('onRentedLot'); + group.removeControl('annualPropertyTax'); + group.removeControl('rentPaid'); + return group; + } + + public clearLienPositionInfoRequirement(group: FormGroup): FormGroup { + CommonGroups.LIEN_POSITION_INFO.fields.forEach((eachField) => { + group.removeControl(eachField.field); + }); + + return group; + } + + public getApplicantName(): string { + return this.formatService.formatTitleCase( + this.applicationWizardService.getApplicantName(BorrowerType.PRIMARY_BORROWER) + ); + } + + public getFirstMortgagePayoff(): boolean { + return this.applicationWizardService.getFieldValue('payoffFirstMortgage'); + } + + public getSecondMortgagePayoff(): boolean { + return this.applicationWizardService.getFieldValue('payoffSecondMortgage'); + } + + public isHelocRateMod(): boolean { + return this.applicationWizardService.getGroupValues('loanType').title === LoanTitle.HELOC_RateMod; + } + + public isMfgHome(): boolean { + return this.applicationWizardService.getGroupValues('loanType').title === LoanTitle.ManufacturedHome; + } + + public getLoanId(): string { + return this.applicationWizardService.getFieldValue('loanId'); + } + + public getFileUploadValues(): any { + + let fileUploadValues = {} + switch(this.applicationWizardService.getGroupValues('loanType').title as LoanTitle){ + case LoanTitle.ManufacturedHome: + fileUploadValues["section"] = ""; + fileUploadValues["sectionId"] = "Collateral"; + fileUploadValues["description"] = "Manufactured Home Title / Purchase Contract"; + break; + case LoanTitle.HELOC: + case LoanTitle.HomeEquityFixed: + case LoanTitle.MortgageRefinanceExpress: + fileUploadValues["section"] = ""; + fileUploadValues["sectionId"] = "Collateral"; + fileUploadValues["description"] = "Copy of homeowners insurance"; + fileUploadValues["label"] = "a copy of the homeowners insurance"; + break; + default: + fileUploadValues = null; + break; + } + + return fileUploadValues; + + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.ts new file mode 100644 index 0000000..4e6fc30 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/collateral-info-page.ts @@ -0,0 +1,22 @@ +import { ApplicationPage } from '../../application-page'; +import { CollateralInformation } from '../../field-groups/equity/collateral-information'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; + +export class CollateralInfoPage extends ApplicationPage { + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + let value; + const errors = []; + + const collateralInfo: CollateralInformation = applicationWizardService.getGroupValues('collateralInformation'); + + this.fieldgroup.forEach((pageFieldGroup) => { + pageFieldGroup.fields.forEach((pageField) => { + value = collateralInfo[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }, this); + }, this); + return errors; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.html new file mode 100644 index 0000000..01f4ead --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.html @@ -0,0 +1,34 @@ +
+
+
+
+

HEADINGS.GET_STARTED

+
+
+
+
+
+
+
+ + + + +
+
+ +
+ +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.spec.ts new file mode 100644 index 0000000..f5d9cbe --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.spec.ts @@ -0,0 +1,195 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule, FormGroup } from '@angular/forms'; +import { NO_ERRORS_SCHEMA, Component, Input } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { EquityMemberInfoPageComponent } from './equity-member-info-page.component'; +import { EquityMemberInfoPageService } from './equity-member-info-page.service'; +import { WindowService } from '../../../shared/window.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { CreditApplicationValues } from '../../../shared/credit-application-values'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { UiSettings } from 'src/app/shared/configuration/ui-settings'; +import { DefaultSettings } from 'src/app/shared/configuration/default-settings.mock'; +import { configureTestSuite } from 'ng-bullet'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { LoanTypeSet } from 'src/app/shared/loan-type'; + +@Component({ + selector: 'lw-member-info-form', + template: '

employment info form stub

' +}) +class MockEquityMemberInfoFormComponent { + + @Input() + public group: FormGroup; + + constructor() { } + +} + +describe('EquityMemberInfoPageComponent', () => { + let component: EquityMemberInfoPageComponent; + let fixture: ComponentFixture; + let service: EquityMemberInfoPageService; + let applicationWizardService: ApplicationWizardService; + let windowService: WindowService; + let configurationService: ConfigurationService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + EquityMemberInfoPageComponent, + MockEquityMemberInfoFormComponent + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [ NO_ERRORS_SCHEMA ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(EquityMemberInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(EquityMemberInfoPageService); + windowService = TestBed.get(WindowService); + configurationService = TestBed.get(ConfigurationService); + configurationService.saveConfiguration(defaultSettings); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.pageForm).toBeTruthy(); + expect(component.equityMemberInfoGroup).toBeTruthy(); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + fixture.detectChanges(); + spyOn(service, 'saveAndNavigate'); + component.localState.nextDisabled = false; + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + }); + + it('should use the window service to print.', () => { + fixture.detectChanges(); + spyOn(windowService, 'showWindowForId').and.returnValue(undefined); + component.commandShowPage(); + expect(windowService.showWindowForId).toHaveBeenCalled(); + }); + + it('should require add/remove controls to memberInfoGroup if "yes" is chosen for co applicant', () => { + fixture.detectChanges(); + expect(component.equityMemberInfoGroup.get('coApplicantLivesInMaritalPropertyState')).toBeFalsy(); + component.updateLocalState({ + applicantType: CreditApplicationValues.CHOICE_YES + }); + fixture.detectChanges(); + expect(component.equityMemberInfoGroup.get('coApplicantLivesInMaritalPropertyState')).toBeTruthy(); + component.updateLocalState({ + applicantType: CreditApplicationValues.CHOICE_NO + }); + fixture.detectChanges(); + expect(component.equityMemberInfoGroup.get('coApplicantLivesInMaritalPropertyState')).toBeFalsy(); + }); + + it('should require addtl controls if "yes" is chosen for co applicant', () => { + fixture.detectChanges(); + expect(component.equityMemberInfoGroup.get('coApplicantLivesInMaritalPropertyState')).toBeFalsy(); + const data = { + applicantType: CreditApplicationValues.CHOICE_NO, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + termOfLoan: 120, + purposeOfLoan: 'Refinance', + agreementRequirement: true + }; + component.updateLocalState(data); + component.equityMemberInfoGroup.patchValue(data); + fixture.detectChanges(); + expect(component.equityMemberInfoGroup.valid).toEqual(true); + data.applicantType = CreditApplicationValues.CHOICE_YES; + component.updateLocalState(data); + component.equityMemberInfoGroup.patchValue(data); + component.equityMemberInfoGroup.controls.coApplicantLivesInMaritalPropertyState.updateValueAndValidity(); + fixture.detectChanges(); + expect(component.equityMemberInfoGroup.valid).toEqual(false); + const expandedData = { + coApplicantLivesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + applicantType: CreditApplicationValues.CHOICE_YES, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + confirmJointCredit: CreditApplicationValues.CHOICE_YES, + termOfLoan: 120, + purposeOfLoan: 'Refinance', + agreementRequirement: true + }; + component.updateLocalState(expandedData); + component.equityMemberInfoGroup.patchValue(expandedData); + component.equityMemberInfoGroup.controls.coApplicantLivesInMaritalPropertyState.updateValueAndValidity(); + fixture.detectChanges(); + expect(component.equityMemberInfoGroup.valid).toEqual(true); + }); + + it('should command the modal to open when applicant fails to confirm joint credit', async(done) => { + fixture.detectChanges(); + component.openModal.subscribe(nextValues => { + expect(nextValues).toBe(true); + done(); + }); + const data = { + coApplicantLivesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + applicantType: CreditApplicationValues.CHOICE_YES, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + confirmJointCredit: CreditApplicationValues.CHOICE_NO + }; + component.updateLocalState(data); + fixture.detectChanges(); + }); + + it('should NOT command the modal to open when applicant confirms joint credit', async(done) => { + fixture.detectChanges(); + component.openModal.subscribe(nextValues => { + expect(nextValues).toBe(false); + done(); + }); + const data = { + coApplicantLivesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + applicantType: CreditApplicationValues.CHOICE_YES, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_NO, + limitRequested: 10000, + confirmJointCredit: CreditApplicationValues.CHOICE_YES + }; + component.updateLocalState(data); + fixture.detectChanges(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.ts new file mode 100644 index 0000000..ff406c0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.component.ts @@ -0,0 +1,146 @@ +import { Component, OnInit, OnDestroy, AfterContentInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { EquityMemberInfoPageService } from './equity-member-info-page.service'; +import { CreditApplication } from '../../../shared/credit-application'; +import { WindowService } from '../../../shared/window.service'; +import { CreditApplicationValues } from 'src/app/shared/credit-application-values'; +import { Subscription, Subject } from 'rxjs'; + +@Component({ + selector: 'lw-equity-member-info-page', + templateUrl: './equity-member-info-page.component.html' +}) +export class EquityMemberInfoPageComponent implements OnInit, OnDestroy, AfterContentInit { + + public pageForm: FormGroup; + public equityMemberInfoGroup: FormGroup; + public applicationTypeMessage: string; + public amountRequestedName = 'Loan Amount'; + + public askLoanPurpose: boolean; + public askTermDuration: boolean; + + private valueSubscription: Subscription; + private statusSubscription: Subscription; + public openModal: Subject; + + public savingData = null; + + private readonly FORM_STATE_INVALID = 'INVALID'; + + public localState = { + nextDisabled: true, + nextTopic: '', + hasCoApplicant: false, + askLoanPurpose: false, + askTermDuration: false, + showCycleCopy: false, + isSpecialLoan: false, + isHelocRateMod: false, + isModalOpen: false, + isHelocOrFixedLoan: false, + beforeYouBeginMessage: null, + requireAgreement: false + }; + + + constructor( + private equityMemberInfoPageService: EquityMemberInfoPageService, + private route: ActivatedRoute, + private windowService: WindowService) { } + + public ngOnInit() { + this.openModal = new Subject(); + this.pageForm = this.equityMemberInfoPageService.createPageGroup(); + this.patchMemberInfoGroupValues(); + this.equityMemberInfoPageService.getLoanTypeMessage().subscribe( (newMessage) => { + this.applicationTypeMessage = newMessage; + }); + this.localState.showCycleCopy = this.equityMemberInfoPageService.isCycleLoan(); + this.localState.isSpecialLoan = this.equityMemberInfoPageService.isSpecialLoan(); + this.localState.isHelocRateMod = this.equityMemberInfoPageService.isHelocRateMod(); + this.localState.askLoanPurpose = this.equityMemberInfoPageService.requirePurposeOfLoan(); + this.localState.askTermDuration = this.equityMemberInfoPageService.requireTermOfLoan(); + this.localState.isHelocOrFixedLoan = this.equityMemberInfoPageService.isHelocOrHomeEquityFixedLoan(); + this.localState.requireAgreement = this.equityMemberInfoPageService.requireAgreement(); + } + + public ngAfterContentInit() { + this.pageForm.addControl(EquityMemberInfoPageService.EQUITY_MEMBER_FORM_NAME, this.equityMemberInfoGroup); + this.localState.nextDisabled = this.pageForm.status === this.FORM_STATE_INVALID; + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + } + + public ngOnDestroy() { + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public updateLocalState(pageValues): void { + if (this.valueSubscription) { + this.valueSubscription.unsubscribe(); + } + + this.updateCoApplicantFields(pageValues); + this.localState.hasCoApplicant = pageValues.applicantType === CreditApplicationValues.CHOICE_YES; + this.localState.nextTopic = this.equityMemberInfoPageService.getNextTopicMessage(); + this.localState.beforeYouBeginMessage = this.equityMemberInfoPageService.getBeforeYouBeginMessage(); + + if (pageValues.applicantType === CreditApplicationValues.CHOICE_YES && + pageValues.confirmJointCredit === CreditApplicationValues.CHOICE_NO && + !this.localState.isModalOpen) { + this.localState.isModalOpen = true; + this.openModal.next(true); + } else { + this.localState.isModalOpen = false; + this.openModal.next(false); + } + + this.valueSubscription = this.equityMemberInfoGroup.valueChanges.subscribe((pageValue) => { + this.updateLocalState(pageValue); + }); + } + + public updateCoApplicantFields(pageValues) { + if (pageValues.applicantType === CreditApplicationValues.CHOICE_YES && !this.equityMemberInfoGroup.get('confirmJointCredit')) { + this.equityMemberInfoPageService.addCoApplicantFieldsToMemberGroup(this.equityMemberInfoGroup); + } else if (pageValues.applicantType === CreditApplicationValues.CHOICE_NO && this.equityMemberInfoGroup.get('confirmJointCredit')) { + this.equityMemberInfoPageService.removeCoApplicantFieldsFromMemberGroup(this.equityMemberInfoGroup); + } + } + + public patchMemberInfoGroupValues() { + this.equityMemberInfoGroup = this.equityMemberInfoPageService.createEquityMemberInformationGroup(); + const formatted = Object.assign( + this.equityMemberInfoPageService.getCommonMemberInfoGroupValues(), + this.equityMemberInfoPageService.getEquityMemberInfoValues() + ); + + if (formatted.applicantType !== CreditApplicationValues.CHOICE_YES) { + this.equityMemberInfoPageService.removeCoApplicantFieldsFromMemberGroup(this.equityMemberInfoGroup); + } + this.equityMemberInfoGroup.patchValue(formatted); + this.updateLocalState(formatted); + } + + public changeApplicantTypeToIndividual(): void { + this.equityMemberInfoGroup.patchValue({applicantType: CreditApplicationValues.CHOICE_NO}); + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + this.equityMemberInfoPageService.saveAndNavigate(this.equityMemberInfoGroup.value, this.route); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + } + + public commandShowPage(): void { + this.windowService.showWindowForId('electronic-disclosures-agreement-container'); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.service.spec.ts new file mode 100644 index 0000000..4f9dd53 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.service.spec.ts @@ -0,0 +1,149 @@ +import { ApplicationWizardService } from '../../application-wizard.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { MembershipInformation } from '../../field-groups/member/membership-information'; +import { EquityMemberInfoPageService } from './equity-member-info-page.service'; +import { EquityMemberInformation } from '../../field-groups/equity/equity-member-information'; +import { LoanType, LoanTypeSet } from '../../../shared/loan-type'; +import { LoanCategory } from '../../../shared/loan-type/loan-category'; +import { LoanTitle } from '../../../shared/loan-type/loan-title'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { UiSettings } from 'src/app/shared/configuration/ui-settings'; +import { DefaultSettings } from 'src/app/shared/configuration/default-settings.mock'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; + + +describe('Equity Member Info Page Service', () => { + + let service: EquityMemberInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationService: CreditApplicationService; + let router: Router; + let configurationService: ConfigurationService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(EquityMemberInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + creditApplicationService = TestBed.get(CreditApplicationService); + applicationWizardService.setLoanType(LoanTypeSet[5].id); + router = TestBed.get(Router); + configurationService = TestBed.get(ConfigurationService); + configurationService.saveConfiguration(defaultSettings); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create an equity member info group', () => { + const memberGroup = service.createEquityMemberInformationGroup(); + expect(memberGroup).toBeTruthy(); + CommonGroups.MEMBER_INFO.fields.forEach((eachField) => { + expect(memberGroup.get(eachField.field)).toBeTruthy(); + }, this); + CommonGroups.MEMBER_INFO_TERM_OF_LOAN.fields.forEach((eachField) => { + expect(memberGroup.get(eachField.field)).toBeTruthy(); + }); + CommonGroups.MEMBER_INFO_TERM_OF_LOAN.fields.forEach((eachField) => { + expect(memberGroup.get(eachField.field)).toBeTruthy(); + }); + }); + + it('should navigate the application, on successful save', () => { + spyOn(creditApplicationService, 'saveEquityMemberInformation').and.returnValue(of(true)); + spyOn(router, 'navigate'); + const info = <[MembershipInformation, EquityMemberInformation]>{}; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'saveEquityMemberInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + const info = <[MembershipInformation, EquityMemberInformation]>{}; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should determine if term of loan is required', () => { + const originalGetGroupValues = applicationWizardService.getGroupValues; + let currentLoanType: LoanType = { ...LoanTypeSet[4] }; + const getGroupValuesSpy = spyOn(applicationWizardService, 'getGroupValues').and.callFake((groupName: string) => { + if (groupName === 'loanType') { + return currentLoanType; + } + return originalGetGroupValues.call(applicationWizardService, groupName); + }); + + expect(service.requireTermOfLoan()).toBe(false); + + currentLoanType = { ...currentLoanType, title: LoanTitle.HomeEquityFixed, category: LoanCategory.Equity, requiresCollateral: true }; + expect(service.requireTermOfLoan()).toBe(true); + + currentLoanType = { ...currentLoanType, title: LoanTitle.Vehicle, category: LoanCategory.Vehicle, requiresCollateral: true }; + expect(service.requireTermOfLoan()).toBe(false); + + currentLoanType = { ...currentLoanType, title: LoanTitle.VehiclePreApproval, category: LoanCategory.Vehicle, requiresCollateral: false }; + expect(service.requireTermOfLoan()).toBe(true); + + currentLoanType = { ...currentLoanType, title: LoanTitle.Signature, category: LoanCategory.Signature, requiresCollateral: false }; + expect(service.requireTermOfLoan()).toBe(true); + + getGroupValuesSpy.and.callThrough(); + }); + + it('should determine if purpose of loan is required', () => { + const originalGetGroupValues = applicationWizardService.getGroupValues; + let currentLoanType: LoanType = { ...LoanTypeSet[0] }; + const getGroupValuesSpy = spyOn(applicationWizardService, 'getGroupValues').and.callFake((groupName: string) => { + if (groupName === 'loanType') { + return currentLoanType; + } + return originalGetGroupValues.call(applicationWizardService, groupName); + }); + + expect(service.requirePurposeOfLoan()).toBe(false); + + currentLoanType = { ...currentLoanType, category: LoanCategory.Equity }; + expect(service.requirePurposeOfLoan()).toBe(true); + + currentLoanType = { ...currentLoanType, category: LoanCategory.Vehicle }; + expect(service.requirePurposeOfLoan()).toBe(false); + + currentLoanType = { ...currentLoanType, category: LoanCategory.Signature }; + expect(service.requirePurposeOfLoan()).toBe(false); + + getGroupValuesSpy.and.callThrough(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.service.ts new file mode 100644 index 0000000..8bea279 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.service.ts @@ -0,0 +1,189 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { EquityMemberInformation } from '../../field-groups/equity/equity-member-information'; +import { MembershipInformation } from '../../field-groups/member/membership-information'; +import { PageService } from '../page-service'; +import { LoanType } from '../../../shared/loan-type'; +import { ConfigurationService } from 'src/app/shared/configuration/configuration.service'; +import { Observable } from 'rxjs'; +import { LoanTitle } from 'src/app/shared/loan-type/loan-title'; +import { LoanCategory } from 'src/app/shared/loan-type/loan-category'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; +import { MemberInfoFormService } from '../../field-groups/member/member-info-form.service'; + +@Injectable({ + providedIn: 'root' +}) +export class EquityMemberInfoPageService implements PageService { + + public static readonly EQUITY_MEMBER_FORM_NAME = 'equityMemberInformation'; + public static readonly MEMBER_FORM_NAME = 'memberInformation'; + private readonly CURRENT_ROUTE = 'step-1'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private configurationService: ConfigurationService, + private router: Router, + private formBuilder: FormBuilder, + private memberService: MemberInfoFormService) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + + return group; + } + + public createEquityMemberInformationGroup(): FormGroup { + const equityMemberGroup = this.formBuilder.group({}); + const loanType = this.applicationWizardService.getGroupValues('loanType'); + const loanCategory = this.memberService.getLoanCategory(); + CommonGroups.MEMBER_INFO.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + validators.push(validationFunction(loanCategory, loanType, this.configurationService)); + }, this); + } + equityMemberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + + if (this.requireTermOfLoan()) { + CommonGroups.MEMBER_INFO_TERM_OF_LOAN.fields.forEach( ( eachField ) => { + equityMemberGroup.addControl( eachField.field, this.formBuilder.control( '', eachField.validation.slice() ) ); + }, this ); + } + + if (this.requirePurposeOfLoan()) { + CommonGroups.MEMBER_INFO_PURPOSE_OF_LOAN.fields.forEach( ( eachField ) => { + equityMemberGroup.addControl( eachField.field, this.formBuilder.control( '', eachField.validation.slice() ) ); + }, this ); + } + + if (this.requirePurposeOfLoanSignature()) { + CommonGroups.MEMBER_INFO_PURPOSE_OF_LOAN_SIGNATURE.fields.forEach( ( eachField ) => { + equityMemberGroup.addControl( eachField.field, this.formBuilder.control( '', eachField.validation.slice() ) ); + }, this ); + } + + if (this.isSpecialLoan()) { + CommonGroups.SPECIAL.fields.forEach( ( eachField ) => { + equityMemberGroup.addControl( eachField.field, this.formBuilder.control( '', eachField.validation.slice() ) ); + }, this ); + } + + this.addCoApplicantFieldsToMemberGroup(equityMemberGroup); + this.addAgreementRequirement(equityMemberGroup); + + return equityMemberGroup; + } + + public addCoApplicantFieldsToMemberGroup(formGroup: FormGroup) { + CommonGroups.CO_APP_MEMBER_INFO.fields.forEach((eachField) => { + formGroup.addControl(eachField.field, this.formBuilder.control('', [])); + }, this); + } + + public addAgreementRequirement(formGroup: FormGroup) { + + if (this.requireAgreement()) { + const loanType = this.applicationWizardService.getGroupValues('loanType'); + CommonGroups.MEMBER_AGREEMENT_REQUIREMENT.fields.forEach((eachField) => { + const validators = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + validators.push(validationFunction(loanType, this.configurationService)); + }, this); + } + formGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + } + } + + public removeCoApplicantFieldsFromMemberGroup(formGroup: FormGroup) { + formGroup.removeControl('confirmJointCredit'); + formGroup.removeControl('coApplicantLivesInMaritalPropertyState'); + } + + public getEquityMemberInfoValues(): EquityMemberInformation { + return this.creditApplicationService.getEquityMemberInformation(); + } + + public getCommonMemberInfoGroupValues(): MembershipInformation { + return this.creditApplicationService.getMemberInformation(); + } + + public isHelocOrHomeEquityFixedLoan(): boolean { + return this.applicationWizardService.getGroupValues('loanType').apiCode === LoanCode.EQ_HELOC_Fixed || + this.applicationWizardService.getGroupValues('loanType').apiCode === LoanCode.EQ_HELOC_Flex; + } + + public saveAndNavigate(equityMemberInfoData: [MembershipInformation, EquityMemberInformation], route: ActivatedRoute) { + this.creditApplicationService.saveEquityMemberInformation(equityMemberInfoData).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], {relativeTo: route}); + } + }, + (failure) => { + // notify user, server, log, etc. + } + ); + } + + public getNextTopicMessage(): string { + return this.applicationWizardService.getNextTopicMessage(this.CURRENT_ROUTE); + } + + public requireTermOfLoan(): boolean { + const loanType: LoanType = this.applicationWizardService.getGroupValues('loanType'); + if (loanType.title === LoanTitle.HELOC) { + return false; + } + return (loanType.category === LoanCategory.Equity || loanType.category === LoanCategory.Signature + || (loanType.category === LoanCategory.Vehicle && !loanType.requiresCollateral)); + } + + public requirePurposeOfLoan(): boolean { + const loanType = this.applicationWizardService.getGroupValues('loanType'); + return (loanType.category === LoanCategory.Equity); + } + + public requirePurposeOfLoanSignature(): boolean { + const loanType = this.applicationWizardService.getGroupValues('loanType'); + return (loanType.category === LoanCategory.Signature); + } + + public getLoanTypeMessage(): Observable { + return this.applicationWizardService.getLoanMessage(); + } + + public isCycleLoan(): boolean { + return this.applicationWizardService.getGroupValues('loanType').apiCode === LoanCode.VEHICLE_DEFAULT; + } + + public isSpecialLoan(): boolean { + return this.applicationWizardService.getGroupValues('loanType').apiCode === LoanCode.SPECIAL; + } + + public isHelocRateMod(): boolean { + return this.applicationWizardService.getGroupValues('loanType').title === LoanTitle.HELOC_RateMod; + } + + public requireAgreement(): boolean { + return ![LoanTitle.Vehicle, LoanTitle.VehiclePreApproval, LoanTitle.HELOC_RateMod].includes(this.applicationWizardService.getGroupValues('loanType').title); + } + + public getBeforeYouBeginMessage(): object { + return this.applicationWizardService.getBeforeYouBeginMessage(); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.ts new file mode 100644 index 0000000..1d9463d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-member-info-page.ts @@ -0,0 +1,28 @@ +import { ApplicationPage } from '../../application-page'; +import { MembershipInformation } from 'src/app/loan-wizard/field-groups/member/membership-information'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; +import { EquityMemberInformation } from '../../field-groups/equity/equity-member-information'; +import { CreditApplication } from '../../../shared/credit-application'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; + +export class EquityMemberInfoPage extends ApplicationPage { + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + let value; + const errors = []; + const memberInfo = creditApplicationService.getMemberInformation(); + const application: CreditApplication = creditApplicationService.getFullCreditApplication(); + + Object.assign(memberInfo, {purposeOfLoan: application.purposeOfLoan}); + Object.assign(memberInfo, {termOfLoan: application.termOfLoan}); + this.fieldgroup.forEach((pageFieldGroup) => { + pageFieldGroup.fields.forEach((pageField) => { + value = memberInfo[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }, this); + }, this); + return errors; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-outline.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-outline.ts new file mode 100644 index 0000000..6af6567 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-outline.ts @@ -0,0 +1,166 @@ +import { ApplicationPage } from '../../application-page'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { PrimaryEmploymentInfoPage } from '../common/employment/pages/primary-employment-info-page'; +import { PrimaryPersonalInfoPage } from '../common/personal/pages/primary-personal-info-page'; +import { PrimaryAddressInfoPage } from '../common/address/pages/primary-address-info-page'; +import { CollateralInfoPage } from './collateral-info-page'; +import { CreditCardDisclosuresInfoPage } from '../credit-card/credit-card-disclosures-info-page'; +import { EquityMemberInfoPage } from './equity-member-info-page'; +import { SpouseTypeInfoPage } from '../common/spouse-type/spouse-type-info-page'; +import { PrimarySpousePersonalInfoPage } from '../common/personal/pages/primary-spouse-personal-info-page'; +import { CoBorrowerPersonalInfoPage } from '../common/personal/pages/co-borrower-personal-info-page'; +import { CoBorrowerSpousePersonalInfoPage } from '../common/personal/pages/co-borrower-spouse-personal-info-page'; +import { PrimarySpouseAddressInfoPage } from '../common/address/pages/primary-spouse-address-info-page'; +import { CoBorrowerAddressInfoPage } from '../common/address/pages/co-borrower-address-info-page'; +import { CoBorrowerSpouseAddressInfoPage } from '../common/address/pages/co-borrower-spouse-address-info-page'; +import { PrimarySpouseEmploymentInfoPage } from '../common/employment/pages/primary-spouse-employment-info-page'; +import { CoBorrowerEmploymentInfoPage } from '../common/employment/pages/co-borrower-employment-info-page'; +import { CoBorrowerSpouseEmploymentInfoPage } from '../common/employment/pages/co-borrower-spouse-employment-info-page'; +import { PrimaryTattleSpouseInfoPage } from '../common/tattle-spouse/pages/primary-tattle-spouse-info-page'; +import { CoBorrowerTattleSpouseInfoPage } from '../common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page'; +import { PrimaryHmdaInfoPage } from '../common/hmda/pages/primary-hmda-info-page'; +import { CoBorrowerHmdaInfoPage } from '../common/hmda/pages/co-borrower-hmda-info-page'; +import { SummaryInfoPage } from '../common/summary/summary-info-page'; +import { DecisionPage } from '../common/decision/decision-page'; +import { ProcessingPage } from '../common/processing/processing-page'; + +export const EquityPages: Array = [ + /* PRIMARY */ + new EquityMemberInfoPage('PAGETITLES.COMMON.INTRO', 'step-1', + [ + CommonGroups.MEMBER_INFO, + CommonGroups.MEMBER_INFO_PURPOSE_OF_LOAN + ] + ), + new PrimaryPersonalInfoPage('PAGETITLES.COMMON.PERSONAL_INFO', 'step-2', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new PrimaryAddressInfoPage('PAGETITLES.COMMON.ADDRESS_INFO', 'step-3', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new PrimaryHmdaInfoPage('PAGETITLES.EQUITY.HMDA', 'eq-hmda', + [ + CommonGroups.RACE_INFO, + CommonGroups.ETHNICITY_INFO, + CommonGroups.GENDER_INFO + ] + ), + new PrimaryEmploymentInfoPage('PAGETITLES.COMMON.EMPLOYMENT_INFO', 'step-4', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ] + ), + /* PRIMARY_SPOUSE */ + new SpouseTypeInfoPage('PAGETITLES.COMMON.PRIMARY_SPOUSETYPE', + 'applicant-spouse-type', + [ + CommonGroups.IS_FULL_INFO_SPOUSE + ] + ), + new PrimaryTattleSpouseInfoPage('PAGETITLES.COMMON.PRIMARY_TATTLESPOUSE', + 'applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO + ] + ), + new PrimarySpousePersonalInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_PERSONAL_INFO', + 'applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new PrimarySpouseAddressInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_ADDRESS_INFO', + 'applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new PrimarySpouseEmploymentInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_EMPLOYMENT_INFO', + 'applicant-spouse-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ] + ), + /* CO_APPLICANT */ + new CoBorrowerPersonalInfoPage('PAGETITLES.COMMON.COAPP_PERSONAL_INFO', + 'co-applicant-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new CoBorrowerAddressInfoPage('PAGETITLES.COMMON.COAPP_ADDRESS_INFO', + 'co-applicant-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new CoBorrowerHmdaInfoPage('PAGETITLES.EQUITY.COAPP_HMDA', 'co-applicant-eq-hmda', + [ + CommonGroups.RACE_INFO, + CommonGroups.ETHNICITY_INFO, + CommonGroups.GENDER_INFO + ] + ), + new CoBorrowerEmploymentInfoPage('PAGETITLES.COMMON.COAPP_EMPLOYMENT_INFO', + 'co-applicant-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* CO_APPLICANT_SPOUSE */ + new SpouseTypeInfoPage('PAGETITLES.COMMON.COAPP_SPOUSETYPE', + 'co-applicant-spouse-type', + [ + CommonGroups.IS_FULL_INFO_SPOUSE + ] + ), + new CoBorrowerTattleSpouseInfoPage('PAGETITLES.COMMON.COAPP_TATTLESPOUSE', + 'co-applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO + ] + ), + new CoBorrowerSpousePersonalInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_PERSONAL_INFO', + 'co-applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new CoBorrowerSpouseAddressInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_ADDRESS_INFO', + 'co-applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new CoBorrowerSpouseEmploymentInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_EMPLOYMENT_INFO', + 'co-applicant-spouse-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ] + ), + new CollateralInfoPage('PAGETITLES.EQUITY.COLLATERAL', 'eq-collateral', + [ + CommonGroups.COLLATERAL_INFO, + CommonGroups.LIEN_POSITION_INFO + ] + ), + new CreditCardDisclosuresInfoPage('PAGETITLES.COMMON.DISCLOSURES', 'cc-disclosures', + [ + CommonGroups.CC_DISCLOSURE_INFO // TODO: Make a different disclosure group, or change the name. + ] + ), + new SummaryInfoPage('PAGETITLES.COMMON.SUMMARY', 'cc-summary', []), + new ProcessingPage('PAGETITLES.CC.PROCESS', 'process', []), + new DecisionPage('PAGETITLES.CC.INTRO', 'decision', []) +]; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-routing.module.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-routing.module.ts new file mode 100644 index 0000000..15ca7c0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/equity/equity-routing.module.ts @@ -0,0 +1,71 @@ +import { Route} from '@angular/router'; +import { PersonalInfoPageComponent } from '../common/personal/personal-info-page.component'; +import { AddressInfoPageComponent } from '../common/address/address-info-page.component'; +import { EmploymentInfoPageComponent } from '../common/employment/employment-info-page.component'; +import { CreditCardDisclosuresPageComponent } from '../credit-card/credit-card-disclosures-page.component'; +import { SummaryPageComponent } from '../common/summary/summary-page.component'; +import { DecisionPageComponent } from '../common/decision/decision-page.component'; +import { LoanRouteGuard } from '../../../shared/guards/loan-route.guard'; +import { BorrowerType } from '../../../shared/borrower-type'; +import { TattleSpouseInfoPageComponent } from '../common/tattle-spouse/tattle-spouse-info-page.component'; +import { SpouseTypeInfoPageComponent } from '../common/spouse-type/spouse-type-info-page.component'; +import { ProcessingPageComponent } from '../common/processing/processing-page.component'; +import { EquityMemberInfoPageComponent } from './equity-member-info-page.component'; +import { HmdaInfoPageComponent } from '../common/hmda/hmda-info-page.component'; +import { CollateralInfoPageComponent } from './collateral-info-page.component'; +import { StepperControllerGuard } from 'src/app/shared/guards/stepper-controller-guard'; +import { MemberCoreResolver } from 'src/app/member/member-core.resolver'; +import { environment } from 'src/environments/environment'; + + +export const eqRoutes: Route = { + path: 'eq/:type', + resolve: environment.coreResolver ? { CoreIndividual: MemberCoreResolver} : {}, + canActivate: [StepperControllerGuard], + canActivateChild: [LoanRouteGuard], + children: [ + { path: 'step-1', component: EquityMemberInfoPageComponent }, + { path: 'step-2', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'step-3', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'eq-hmda', component: HmdaInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'step-4', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'applicant-spouse-type', component: SpouseTypeInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'applicant-spouse', component: TattleSpouseInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'co-applicant-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-eq-hmda', component: HmdaInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-spouse-type', component: SpouseTypeInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-spouse', component: TattleSpouseInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'eq-collateral', component: CollateralInfoPageComponent }, + { path: 'cc-disclosures', component: CreditCardDisclosuresPageComponent }, + { path: 'cc-summary', component: SummaryPageComponent }, + { path: 'process', component: ProcessingPageComponent }, + { path: 'decision', component: DecisionPageComponent }, + { path: '', redirectTo: 'step-1', pathMatch: 'prefix' } + ] +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/page-service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/page-service.ts new file mode 100644 index 0000000..cf6a3e9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/page-service.ts @@ -0,0 +1,7 @@ +import { BorrowerType } from '../../shared/borrower-type'; + +export interface PageService { + + getNextTopicMessage(individual?: BorrowerType): string; + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/signature/signature-loan-outline.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/signature/signature-loan-outline.ts new file mode 100644 index 0000000..537e274 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/signature/signature-loan-outline.ts @@ -0,0 +1,138 @@ +import { ApplicationPage } from '../../application-page'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { MemberInfoPage } from '../common/member/member-info-page'; +import { PrimaryEmploymentInfoPage } from '../common/employment/pages/primary-employment-info-page'; +import { PrimaryPersonalInfoPage } from '../common/personal/pages/primary-personal-info-page'; +import { PrimaryAddressInfoPage } from '../common/address/pages/primary-address-info-page'; +import { PrimaryTattleSpouseInfoPage } from '../common/tattle-spouse/pages/primary-tattle-spouse-info-page'; +import { SpouseTypeInfoPage } from '../common/spouse-type/spouse-type-info-page'; +import { PrimarySpousePersonalInfoPage } from '../common/personal/pages/primary-spouse-personal-info-page'; +import { CoBorrowerPersonalInfoPage } from '../common/personal/pages/co-borrower-personal-info-page'; +import { CoBorrowerSpousePersonalInfoPage } from '../common/personal/pages/co-borrower-spouse-personal-info-page'; +import { PrimarySpouseAddressInfoPage } from '../common/address/pages/primary-spouse-address-info-page'; +import { CoBorrowerAddressInfoPage } from '../common/address/pages/co-borrower-address-info-page'; +import { CoBorrowerSpouseAddressInfoPage } from '../common/address/pages/co-borrower-spouse-address-info-page'; +import { PrimarySpouseEmploymentInfoPage } from '../common/employment/pages/primary-spouse-employment-info-page'; +import { CoBorrowerEmploymentInfoPage } from '../common/employment/pages/co-borrower-employment-info-page'; +import { CoBorrowerSpouseEmploymentInfoPage } from '../common/employment/pages/co-borrower-spouse-employment-info-page'; +import { CoBorrowerTattleSpouseInfoPage } from '../common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page'; +import { SummaryInfoPage } from '../common/summary/summary-info-page'; +import { DecisionPage } from '../common/decision/decision-page'; +import { ProcessingPage } from '../common/processing/processing-page'; +import { CreditCardDisclosuresInfoPage } from '../credit-card/credit-card-disclosures-info-page'; +import { EquityMemberInfoPage } from '../equity/equity-member-info-page'; + +export const SignatureLoanPages: Array = [ + /* PRIMARY */ + new EquityMemberInfoPage('PAGETITLES.COMMON.INTRO', + 'step-1', + [ + CommonGroups.MEMBER_INFO + ]), + new PrimaryPersonalInfoPage('PAGETITLES.COMMON.PERSONAL_INFO', + 'step-2', + [ + CommonGroups.PERSONAL_INFO + ]), + new PrimaryAddressInfoPage('PAGETITLES.COMMON.ADDRESS_INFO', + 'step-3', + [ + CommonGroups.ADDRESS_INFO + ]), + new PrimaryEmploymentInfoPage('PAGETITLES.COMMON.EMPLOYMENT_INFO', + 'step-4', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* PRIMARY_SPOUSE */ + new SpouseTypeInfoPage('PAGETITLES.COMMON.PRIMARY_SPOUSETYPE', + 'applicant-spouse-type', + [ + CommonGroups.IS_FULL_INFO_SPOUSE + ]), + new PrimaryTattleSpouseInfoPage('PAGETITLES.COMMON.PRIMARY_TATTLESPOUSE', + 'applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO + ]), + new PrimarySpousePersonalInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_PERSONAL_INFO', + 'applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new PrimarySpouseAddressInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_ADDRESS_INFO', + 'applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new PrimarySpouseEmploymentInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_EMPLOYMENT_INFO', + 'applicant-spouse-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* CO_APPLICANT */ + new CoBorrowerPersonalInfoPage('PAGETITLES.COMMON.COAPP_PERSONAL_INFO', + 'co-applicant-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new CoBorrowerAddressInfoPage('PAGETITLES.COMMON.COAPP_ADDRESS_INFO', + 'co-applicant-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new CoBorrowerEmploymentInfoPage('PAGETITLES.COMMON.COAPP_EMPLOYMENT_INFO', + 'co-applicant-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* CO_APPLICANT_SPOUSE */ + new SpouseTypeInfoPage('PAGETITLES.COMMON.COAPP_SPOUSETYPE', + 'co-applicant-spouse-type', + [ + CommonGroups.IS_FULL_INFO_SPOUSE + ]), + new CoBorrowerTattleSpouseInfoPage('PAGETITLES.COMMON.COAPP_TATTLESPOUSE', + 'co-applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO + ]), + new CoBorrowerSpousePersonalInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_PERSONAL_INFO', + 'co-applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new CoBorrowerSpouseAddressInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_ADDRESS_INFO', + 'co-applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new CoBorrowerSpouseEmploymentInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_EMPLOYMENT_INFO', + 'co-applicant-spouse-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* DISCLOSURE / SUMMARY */ + new CreditCardDisclosuresInfoPage('PAGETITLES.COMMON.DISCLOSURES', + 'cc-disclosures', + [ + CommonGroups.CC_DISCLOSURE_INFO + ]), + new SummaryInfoPage('PAGETITLES.COMMON.SUMMARY', 'cc-summary', []), + new ProcessingPage('PAGETITLES.CC.PROCESS', 'process', []), + new DecisionPage('PAGETITLES.COMMON.DECISION', 'decision', []) +]; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/signature/signature-routing.module.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/signature/signature-routing.module.ts new file mode 100644 index 0000000..d30f83c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/signature/signature-routing.module.ts @@ -0,0 +1,64 @@ +import { Route} from '@angular/router'; +import { PersonalInfoPageComponent } from '../common/personal/personal-info-page.component'; +import { AddressInfoPageComponent } from '../common/address/address-info-page.component'; +import { EmploymentInfoPageComponent } from '../common/employment/employment-info-page.component'; +import { SummaryPageComponent } from '../common/summary/summary-page.component'; +import { DecisionPageComponent } from '../common/decision/decision-page.component'; +import { LoanRouteGuard } from '../../../shared/guards/loan-route.guard'; +import { BorrowerType } from '../../../shared/borrower-type'; +import { TattleSpouseInfoPageComponent } from '../common/tattle-spouse/tattle-spouse-info-page.component'; +import { SpouseTypeInfoPageComponent } from '../common/spouse-type/spouse-type-info-page.component'; +import { ProcessingPageComponent } from '../common/processing/processing-page.component'; +import { CreditCardDisclosuresPageComponent } from '../credit-card/credit-card-disclosures-page.component'; +import { EquityMemberInfoPageComponent } from '../equity/equity-member-info-page.component'; +import { StepperControllerGuard } from 'src/app/shared/guards/stepper-controller-guard'; +import { MemberCoreResolver } from 'src/app/member/member-core.resolver'; +import { environment } from 'src/environments/environment'; + + +export const slRoutes: Route = { + path: 'sl/:type', + resolve: environment.coreResolver ? { CoreIndividual: MemberCoreResolver} : {}, + canActivate: [StepperControllerGuard], + canActivateChild: [LoanRouteGuard], + children: [ + { path: 'step-1', component: EquityMemberInfoPageComponent }, + { path: 'step-2', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'step-3', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'step-4', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'applicant-spouse-type', component: SpouseTypeInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'applicant-spouse', component: TattleSpouseInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'co-applicant-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-spouse-type', component: SpouseTypeInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-spouse', component: TattleSpouseInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'cc-disclosures', component: CreditCardDisclosuresPageComponent }, + { path: 'cc-summary', component: SummaryPageComponent }, + { path: 'process', component: ProcessingPageComponent }, + { path: 'decision', component: DecisionPageComponent }, + { path: '', redirectTo: 'step-1', pathMatch: 'prefix' } + ] +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.html new file mode 100644 index 0000000..04ea4cf --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.html @@ -0,0 +1,30 @@ +
+
+
+

+
+
+
+ + +
+ +
+ + +
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.spec.ts new file mode 100644 index 0000000..64569ea --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.spec.ts @@ -0,0 +1,97 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { VehicleCollateralInfoPageComponent } from './vehicle-collateral-info-page.component'; +import { VehicleCollateralInfoPageService } from './vehicle-collateral-info-page.service'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +describe('VehicleCollateralInfoPageComponent', () => { + let component: VehicleCollateralInfoPageComponent; + let fixture: ComponentFixture; + let service: VehicleCollateralInfoPageService; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + VehicleCollateralInfoPageComponent + ], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[10].id); + fixture = TestBed.createComponent(VehicleCollateralInfoPageComponent); + component = fixture.componentInstance; + service = TestBed.get(VehicleCollateralInfoPageService); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + expect(component.vehicleInfoGroup).toBeTruthy(); + }); + + it('should bind the next button to the correct command function', async(() => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonNext')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).toHaveBeenCalled(); + expect(component.commandPrevious).not.toHaveBeenCalled(); + }); + })); + + it('should use the service to navigate', () => { + fixture.detectChanges(); + spyOn(service, 'saveAndNavigate'); + component.localState.nextDisabled = false; + component.commandNext(); + expect(service.saveAndNavigate).toHaveBeenCalled(); + spyOn(service, 'cancelAndNavigate'); + component.commandPrevious(); + expect(service.cancelAndNavigate).toHaveBeenCalled(); + }); + + it('should bind the previous button to the correct command function', async () => { + spyOn(component, 'commandNext'); + spyOn(component, 'commandPrevious'); + + const button = fixture.debugElement.query(By.css('#buttonPrevious')).nativeElement; + button.click(); + + fixture.whenStable().then(() => { + expect(component.commandNext).not.toHaveBeenCalled(); + expect(component.commandPrevious).toHaveBeenCalled(); + }); + }); + + it('should patch in loan term correctly (must convert to "int" for dropdown', () => { + spyOn(applicationWizardService, 'getFieldValue').and.returnValue('48'); + + fixture.detectChanges(); + expect(component.vehicleInfoGroup.value.term).toBe(48); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.ts new file mode 100644 index 0000000..1be8fa2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.component.ts @@ -0,0 +1,161 @@ +import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { VehicleCollateralInfoPageService } from './vehicle-collateral-info-page.service'; +import { Subject, Subscription } from 'rxjs'; +import { LoanPurpose } from 'src/app/shared/loan-type/loan-purpose'; +import { CreditApplicationValues } from '../../../shared/credit-application-values'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { AddressFormBuilderService } from 'src/app/shared/address-validation/address-form-builder.service'; +import { AddressInformation } from '../../field-groups/address/address-information'; +import { AddressDiffInformation } from '../../field-groups/address-compare/address-compare-information'; +import { AddressValidationFormatConverter } from 'src/app/shared/address-validation/address-validation-format-converter'; + +@Component({ + selector: 'lw-collateral-vehicle-info-page', + templateUrl: './vehicle-collateral-info-page.component.html' +}) +export class VehicleCollateralInfoPageComponent implements OnInit, OnDestroy { + + public savingData = null; + + public localState = { + previousDisabled: false, + nextDisabled: true, + nextTopic: '', + inputYear: 0, + inputType: '', + borrowerName: 'Applicant', + requireFullCollateral: true, + collateralType: 'vehicle', + requireSellerInput: true, + savingStep: null, + isTrailerMotorhome: false, + isPowerSport: false, + isVehicleDefault: false, + isBoat: false, + isTransferringPlate: false, + requireBoatAddressInput: false, + boatAddressInformation: null, + hasLien: false + }; + private subscription: Subscription; + public vehicleInfoGroup: FormGroup; + public boatAddressGroup: FormGroup; + public boatAddressVerifyGroup: FormGroup; + public loanId: string; + public pageForm: FormGroup; + public loanCode: LoanCode; + + private readonly FORM_STATE_INVALID = 'INVALID'; + + constructor( + private vehicleCollateralInfoPageService: VehicleCollateralInfoPageService, + private route: ActivatedRoute, + private cd: ChangeDetectorRef) {} + + public ngOnInit() { + this.pageForm = this.vehicleCollateralInfoPageService.createPageGroup(); + this.vehicleInfoGroup = this.pageForm.get(VehicleCollateralInfoPageService.VEHICLE_FORM_NAME); + this.loanId = this.vehicleCollateralInfoPageService.getLoanId(); + this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.nextDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + + this.loanCode = this.vehicleCollateralInfoPageService.getLoanCode(); + + this.vehicleInfoGroup.controls.term.patchValue(parseInt(this.vehicleCollateralInfoPageService.getTermLength(), 10)); + + this.vehicleInfoGroup.patchValue( + this.vehicleCollateralInfoPageService.getApplicationGroupValues(VehicleCollateralInfoPageService.VEHICLE_FORM_NAME) + ); + + this.updateLocalState(this.pageForm.value); + } + + public updateLocalState(values) { + this.localState.borrowerName = this.vehicleCollateralInfoPageService.getApplicantName(); + this.localState.nextTopic = this.vehicleCollateralInfoPageService.getNextTopicMessage(); + this.localState.requireFullCollateral = this.vehicleCollateralInfoPageService.requireFullCollateral(); + this.localState.collateralType = this.vehicleCollateralInfoPageService.getCollateralType(); + this.localState.isTrailerMotorhome = this.vehicleCollateralInfoPageService.isLoanId('tmh'); + this.localState.isPowerSport = this.vehicleCollateralInfoPageService.isLoanId('pwc'); + this.localState.isVehicleDefault = this.vehicleCollateralInfoPageService.isLoanId('auto'); + this.localState.isBoat = this.vehicleCollateralInfoPageService.isLoanId('boat'); + this.localState.isTransferringPlate = values.transferring === CreditApplicationValues.CHOICE_YES; + this.localState.requireBoatAddressInput = (this.localState.isBoat && values.boatSameAddress === CreditApplicationValues.CHOICE_NO); + this.localState.hasLien = values.lien === CreditApplicationValues.CHOICE_YES; + this.localState.requireSellerInput = values.loanPurpose === LoanPurpose.Purchase; + + if (this.subscription) { + this.subscription.unsubscribe(); + } + + this.vehicleCollateralInfoPageService.modifyVehicleSellerInfoGroup(this.localState.requireSellerInput, this.vehicleInfoGroup); + this.modifyBoatAddressInfoGroup(); + this.subscription = this.vehicleInfoGroup.valueChanges.subscribe(nextValues => this.updateLocalState(nextValues)); + this.cd.detectChanges(); + } + + public ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public commandNext() { + if (this.localState.nextDisabled) { + return; + } + + this.vehicleCollateralInfoPageService.saveAndNavigate(this.pageForm.value, this.route); + this.savingData = {isActive: true}; + this.localState.nextDisabled = true; + } + + public commandPrevious() { + this.vehicleCollateralInfoPageService.cancelAndNavigate(this.route); + } + + public modifyBoatAddressInfoGroup(): void { + if (this.localState.requireBoatAddressInput && !this.boatAddressGroup) { + this.boatAddressGroup = this.vehicleCollateralInfoPageService.createBoatAddressInfoGroup(); + this.pageForm.addControl(VehicleCollateralInfoPageService.BOAT_ADDRESS_FORM_NAME, this.boatAddressGroup); + this.boatAddressGroup.patchValue({state: CreditApplicationValues.STATE_REQUIRED_COAPP}); + this.patchBoatAddressGroup(); + } else if (!this.localState.requireBoatAddressInput && this.boatAddressGroup) { + this.pageForm.removeControl(VehicleCollateralInfoPageService.BOAT_ADDRESS_FORM_NAME); + this.boatAddressGroup = null; + } + } + + public patchBoatAddressGroup() { + const vehicleInfo = this.vehicleCollateralInfoPageService.getApplicationGroupValues(VehicleCollateralInfoPageService.VEHICLE_FORM_NAME); + if (vehicleInfo.boatAddress) { + this.boatAddressGroup.patchValue(vehicleInfo.boatAddress); + } + } + + public confirmBoatAddressAddressInformation(addressInformation: AddressInformation) { + if (!this.boatAddressVerifyGroup) { + AddressFormBuilderService.addAddressCompareGroupIfMissing(this.pageForm, VehicleCollateralInfoPageService.BOAT_ADDRESS_VERIFY_FORM_NAME); + this.boatAddressVerifyGroup = this.pageForm.get(VehicleCollateralInfoPageService.BOAT_ADDRESS_VERIFY_FORM_NAME); + } + if (!addressInformation) { + this.boatAddressVerifyGroup.patchValue({addressVerifyStatus: false}); + } else { + this.boatAddressVerifyGroup.patchValue({addressVerifyStatus: true}); + this.patchBoatAddressIfFieldsConfirmedAreDifferent(addressInformation); + } + } + + public patchBoatAddressIfFieldsConfirmedAreDifferent(addressInformation: AddressInformation) { + const addressDiff: AddressDiffInformation = AddressValidationFormatConverter.getAddressValidationDiff( + AddressValidationFormatConverter.makeApiAddress(this.boatAddressGroup.value), + addressInformation + ); + if (!addressDiff.allFieldsSame) { + this.boatAddressGroup.patchValue(addressInformation); + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.service.spec.ts new file mode 100644 index 0000000..8a22ef9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.service.spec.ts @@ -0,0 +1,140 @@ +import { ApplicationWizardService } from '../../application-wizard.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { VehicleCollateralInfoPageService } from './vehicle-collateral-info-page.service'; +import {VehicleCollateralInformation} from '../../field-groups/vehicle/vehicle-collateral-information'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; + + +describe('Vehicle Collateral Info Page Service', () => { + + let service: VehicleCollateralInfoPageService; + let applicationWizardService: ApplicationWizardService; + let creditApplicationDaoService: MockCreditApplicationDaoService; + let creditApplicationService: CreditApplicationService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(VehicleCollateralInfoPageService); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[7].id); + creditApplicationDaoService = TestBed.get(MockCreditApplicationDaoService); + creditApplicationService = TestBed.get(CreditApplicationService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create the top level form group', () => { + spyOn(service, 'createPageGroup').and.callThrough(); + service.createPageGroup(); + expect(service.createPageGroup).toHaveBeenCalled(); + }); + + it('should create a vehicle info group', () => { + const collateralGroup = service.createVehicleInformationGroup(); + expect(collateralGroup).toBeTruthy(); + CommonGroups.VEHICLE_COLLATERAL_INFO.fields.forEach((eachField) => { + expect(collateralGroup.get(eachField.field)).toBeTruthy(); + }, this); + + CommonGroups.VEHICLE_SELLER_INFO.fields.forEach((eachField) => { + expect(collateralGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should create a partial vehicle info group', () => { + applicationWizardService.setLoanType(LoanTypeSet[10].id); + const collateralGroup = service.createVehicleInformationGroup(); + expect(collateralGroup).toBeTruthy(); + CommonGroups.VEHICLE_COLLATERAL_INFO_PARTIAL.fields.forEach((eachField) => { + expect(collateralGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); + + it('should determine if full collateral is required', () => { + spyOn(service, 'requireFullCollateral').and.callThrough(); + applicationWizardService.setLoanType(LoanTypeSet[10].id); + let requireFull = service.requireFullCollateral(); + expect(requireFull).toBeFalsy(); + applicationWizardService.setLoanType(LoanTypeSet[7].id); + requireFull = service.requireFullCollateral(); + expect(requireFull).toBeTruthy(); + }); + + it('should grab proper collateral type', () => { + spyOn(service, 'getCollateralType').and.callThrough(); + const boatLoan = LoanTypeSet.find((loanType) => loanType.id === 'boat'); + const vehicleLoan = LoanTypeSet.find((loanType) => loanType.id === 'auto'); + + if (boatLoan) { + applicationWizardService.setLoanType(boatLoan.id); + expect(service.getCollateralType()).toBe(boatLoan.collateralType); + } + + if (vehicleLoan) { + applicationWizardService.setLoanType(vehicleLoan.id); + expect(service.getCollateralType()).toBe(vehicleLoan.collateralType); + } + }); + + it('should provide the value for the groups', () => { + const testGroupName = 'testGroup'; + spyOn(applicationWizardService, 'getGroupValues').and.returnValue(true); + service.getApplicationGroupValues(testGroupName); + expect(applicationWizardService.getGroupValues).toHaveBeenCalledWith(testGroupName); + }); + + it('should navigate the application, on successful save', () => { + spyOn(creditApplicationService, 'saveVehicleCollateralInformation').and.returnValue(of(true)); + spyOn(router, 'navigate'); + const info = {vehicleInformation: {}}; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate the application, if failed save', () => { + spyOn(creditApplicationService, 'saveVehicleCollateralInformation').and.returnValue(of(false)); + spyOn(router, 'navigate'); + const info = {vehicleInformation: {}}; + const activatedRoute = {}; + service.saveAndNavigate(info, activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('cancelling should always navigate', () => { + spyOn(router, 'navigate'); + const activatedRoute = {}; + service.cancelAndNavigate(activatedRoute); + expect(router.navigate).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.service.ts new file mode 100644 index 0000000..0df0cd6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.service.ts @@ -0,0 +1,207 @@ +import { Injectable } from '@angular/core'; +import { FormGroup, FormBuilder, FormControl, Validators, ValidatorFn } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { VehicleCollateralInformation } from '../../field-groups/vehicle/vehicle-collateral-information'; +import { PageService } from '../page-service'; +import { BorrowerType } from '../../../shared/borrower-type'; +import { LoanType } from '../../../shared/loan-type'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; +import { FormatService } from 'src/app/shared/pipes/format.service'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { AddressInformation } from '../../field-groups/address/address-information'; + +@Injectable({ + providedIn: 'root' +}) +export class VehicleCollateralInfoPageService implements PageService { + + public static readonly VEHICLE_FORM_NAME = 'vehicleInformation'; + public static readonly BOAT_ADDRESS_FORM_NAME = 'boatAddressInformation'; + public static readonly BOAT_ADDRESS_VERIFY_FORM_NAME = 'boatAddressVerifyInformation'; + private readonly CURRENT_ROUTE = 've-collateral'; + + constructor( + private applicationWizardService: ApplicationWizardService, + private creditApplicationService: CreditApplicationService, + private formatService: FormatService, + private router: Router, + private formBuilder: FormBuilder) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + const memberGroup = this.createVehicleInformationGroup(); + group.addControl(VehicleCollateralInfoPageService.VEHICLE_FORM_NAME, memberGroup); + return group; + } + + public createVehicleInformationGroup(): FormGroup { + const groupValidator = CommonGroups.VEHICLE_SELLER_INFO.groupValidator(); + const memberGroup = this.formBuilder.group({}, {validator: groupValidator}); + const loanType = this.applicationWizardService.getGroupValues('loanType'); + + if (this.requireFullCollateral()) { + CommonGroups.VEHICLE_COLLATERAL_INFO.fields.forEach((eachField) => { + const validators: Array = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + const validatorFn = validationFunction(loanType); + validators.push(validatorFn); + }, this); + } + memberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + } else { + CommonGroups.VEHICLE_COLLATERAL_INFO_PARTIAL.fields.forEach((eachField) => { + const validators: Array = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + const validatorFn = validationFunction(loanType); + validators.push(validatorFn); + }, this); + } + memberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + } + + CommonGroups.VEHICLE_SELLER_INFO.fields.forEach((eachField) => { + const validators: Array = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + const validatorFn = validationFunction(loanType); + validators.push(validatorFn); + }, this); + } + memberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + + if (this.isBoatLoan()) { + CommonGroups.VEHICLE_BOAT_INFO.fields.forEach((eachField) => { + const validators: Array = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + const validatorFn = validationFunction(); + validators.push(validatorFn); + }, this); + } + memberGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + }, this); + } + + return memberGroup; + } + + public createBoatAddressInfoGroup(): FormGroup { + const memberGroup = this.formBuilder.group({}); + + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + memberGroup.addControl(eachField.field, this.formBuilder.control('')); + }, this); + + CommonGroups.ADDRESS_INFO.fields.forEach((eachField) => { + memberGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + return memberGroup; + } + + public modifyVehicleSellerInfoGroup(requireSellerInput, formGroup) { + const loanType = this.applicationWizardService.getGroupValues('loanType'); + + if (requireSellerInput && !formGroup.get('seller')) { + CommonGroups.VEHICLE_SELLER_INFO.fields.forEach((eachField) => { + const validators: Array = eachField.validation.slice(); + if (eachField.validatorFn) { + eachField.validatorFn.forEach((validationFunction) => { + const validatorFn = validationFunction(loanType); + validators.push(validatorFn); + }, this); + } + formGroup.addControl(eachField.field, this.formBuilder.control('', validators)); + formGroup.get(eachField.field).updateValueAndValidity(); + }, this); + this.patchVehicleSellerInfoGroup(formGroup); + } else if (!requireSellerInput && formGroup.get('seller')) { + CommonGroups.VEHICLE_SELLER_INFO.fields.forEach((eachField) => { + formGroup.removeControl(eachField.field); + }, this); + } + } + + public patchVehicleSellerInfoGroup(formGroup) { + const vehicleInfo = this.getApplicationGroupValues(VehicleCollateralInfoPageService.VEHICLE_FORM_NAME); + CommonGroups.VEHICLE_SELLER_INFO.fields.forEach((eachField) => { + if (vehicleInfo[`${eachField.field}`]) { + formGroup.patchValue({[`${eachField.field}`]: vehicleInfo[`${eachField.field}`]}); + } + }, this); + } + + public requireFullCollateral(): boolean { + const loanId = this.applicationWizardService.getGroupValues('loanType').id; + return !( loanId === 'boat' || loanId === 'pwc' || loanId === 'tmh'); + } + + public isBoatLoan(): boolean { + return this.isLoanId('boat'); + } + + public isLoanId(apiCode: string): boolean { + const loanId = this.applicationWizardService.getGroupValues('loanType').id; + return (loanId === apiCode); + } + + public getApplicationGroupValues(groupName: string): any { + return this.applicationWizardService.getGroupValues(groupName); + } + + public saveAndNavigate(fields: { + vehicleInformation: VehicleCollateralInformation, + boatAddressInformation?: AddressInformation}, route: ActivatedRoute) { + this.creditApplicationService.saveVehicleCollateralInformation(fields).subscribe( + (successFlag: boolean) => { + if (successFlag) { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], { relativeTo: route }); + } else { + const nextRoute = this.applicationWizardService.getNextRoute(this.CURRENT_ROUTE); + this.router.navigate([nextRoute], { relativeTo: route }); + } + }, + (failure) => { + // notify user, server, log, etc. + }); + } + + public cancelAndNavigate( route: ActivatedRoute) { + const prevRoute = this.applicationWizardService.getPreviousRoute(this.CURRENT_ROUTE); + this.router.navigate([prevRoute], { relativeTo: route }); + } + + public getNextTopicMessage(): string { + return this.applicationWizardService.getNextTopicMessage(this.CURRENT_ROUTE); + } + + public getApplicantName(): string { + return this.formatService.formatTitleCase( + this.applicationWizardService.getApplicantName(BorrowerType.PRIMARY_BORROWER) + ); + } + + public getCollateralType(): string { + return this.applicationWizardService.getFieldValue('loanType').collateralType; + } + + public getTermLength(): string { + return this.applicationWizardService.getFieldValue('termOfLoan'); + } + + public getLoanCode(): LoanCode { + return this.applicationWizardService.getFieldValue('loanType').apiCode; + } + + public getLoanId() : string { + return this.applicationWizardService.getFieldValue("loanId"); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.spec.ts new file mode 100644 index 0000000..f30d6a2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.spec.ts @@ -0,0 +1,46 @@ +import { ApplicationWizardService } from '../../application-wizard.service'; +import { ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../../../shared/credit-application-dao.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockCreditApplicationDaoService } from '../../../testing/shared-mocks/credit-application-dao.mock.service'; +import { LoanTypeSet } from '../../../shared/loan-type'; +import { TranslateModule } from '@ngx-translate/core'; +import { VehicleCollateralInfoPage } from './vehicle-collateral-info-page'; +import { CommonGroups } from 'src/app/loan-wizard/field-groups/common-groups'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Vehicle Collateral Info Page', () => { + + let page: VehicleCollateralInfoPage; + let applicationWizardService: ApplicationWizardService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + page = new VehicleCollateralInfoPage('PAGETITLES.VEHICLE.COLLATERAL', 've-collateral', + [ + CommonGroups.VEHICLE_COLLATERAL_INFO + ] + ); + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(page).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.ts new file mode 100644 index 0000000..0d4c3e5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-collateral-info-page.ts @@ -0,0 +1,25 @@ +import { ApplicationPage } from '../../application-page'; +import { ApplicationWizardService } from '../../application-wizard.service'; +import { VehicleCollateralInformation } from '../../field-groups/vehicle/vehicle-collateral-information'; +import { CreditApplicationService } from '../../../shared/credit-application.service'; + +export class VehicleCollateralInfoPage extends ApplicationPage { + + public isValidPage(applicationWizardService: ApplicationWizardService, creditApplicationService: CreditApplicationService): Array { + let value; + const errors = []; + const vehicleCollateralInfo: VehicleCollateralInformation = applicationWizardService.getGroupValues('vehicleInformation'); + this.fieldgroup.forEach((pageFieldGroup) => { + pageFieldGroup.fields.forEach((pageField) => { + value = vehicleCollateralInfo[pageField.field]; + errors.push(...applicationWizardService.getFieldErrors(pageField.field, value, pageField.validation.slice())); + value = undefined; + }, this); + }, this); + return errors; + } + + public determinePageRequired(applicationWizardService: ApplicationWizardService): boolean { + return applicationWizardService.getGroupValues('loanType').id !== 'pauto'; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-outline.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-outline.ts new file mode 100644 index 0000000..7649f11 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-outline.ts @@ -0,0 +1,149 @@ +import { ApplicationPage } from '../../application-page'; +import { CommonGroups } from '../../field-groups/common-groups'; +import { PrimaryEmploymentInfoPage } from '../common/employment/pages/primary-employment-info-page'; +import { PrimaryPersonalInfoPage } from '../common/personal/pages/primary-personal-info-page'; +import { PrimaryAddressInfoPage } from '../common/address/pages/primary-address-info-page'; +import { CreditCardDisclosuresInfoPage } from '../credit-card/credit-card-disclosures-info-page'; +import { VehicleCollateralInfoPage } from './vehicle-collateral-info-page'; +import { SpouseTypeInfoPage } from '../common/spouse-type/spouse-type-info-page'; +import { PrimarySpousePersonalInfoPage } from '../common/personal/pages/primary-spouse-personal-info-page'; +import { CoBorrowerPersonalInfoPage } from '../common/personal/pages/co-borrower-personal-info-page'; +import { CoBorrowerSpousePersonalInfoPage } from '../common/personal/pages/co-borrower-spouse-personal-info-page'; +import { PrimarySpouseAddressInfoPage } from '../common/address/pages/primary-spouse-address-info-page'; +import { CoBorrowerAddressInfoPage } from '../common/address/pages/co-borrower-address-info-page'; +import { CoBorrowerSpouseAddressInfoPage } from '../common/address/pages/co-borrower-spouse-address-info-page'; +import { PrimarySpouseEmploymentInfoPage } from '../common/employment/pages/primary-spouse-employment-info-page'; +import { CoBorrowerEmploymentInfoPage } from '../common/employment/pages/co-borrower-employment-info-page'; +import { CoBorrowerSpouseEmploymentInfoPage } from '../common/employment/pages/co-borrower-spouse-employment-info-page'; +import { PrimaryTattleSpouseInfoPage } from '../common/tattle-spouse/pages/primary-tattle-spouse-info-page'; +import { CoBorrowerTattleSpouseInfoPage } from '../common/tattle-spouse/pages/co-borrower-tattle-spouse-info-page'; +import { SummaryInfoPage } from '../common/summary/summary-info-page'; +import { DecisionPage } from '../common/decision/decision-page'; +import { ProcessingPage } from '../common/processing/processing-page'; +import { EquityMemberInfoPage } from '../equity/equity-member-info-page'; + +export const VehiclePages: Array = [ + /* PRIMARY */ + new EquityMemberInfoPage('PAGETITLES.COMMON.INTRO', 'step-1', + [ + CommonGroups.MEMBER_INFO + ] + ), + new PrimaryPersonalInfoPage('PAGETITLES.COMMON.PERSONAL_INFO', 'step-2', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new PrimaryAddressInfoPage('PAGETITLES.COMMON.ADDRESS_INFO', 'step-3', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new PrimaryEmploymentInfoPage('PAGETITLES.COMMON.EMPLOYMENT_INFO', 'step-4', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ] + ), + /* PRIMARY_SPOUSE */ + new SpouseTypeInfoPage('PAGETITLES.COMMON.PRIMARY_SPOUSETYPE', + 'applicant-spouse-type', + [ + CommonGroups.IS_FULL_INFO_SPOUSE + ] + ), + new PrimaryTattleSpouseInfoPage('PAGETITLES.COMMON.PRIMARY_TATTLESPOUSE', + 'applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO + ] + ), + new PrimarySpousePersonalInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_PERSONAL_INFO', + 'applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new PrimarySpouseAddressInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_ADDRESS_INFO', + 'applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new PrimarySpouseEmploymentInfoPage('PAGETITLES.COMMON.PRIMARYSPOUSE_EMPLOYMENT_INFO', + 'applicant-spouse-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ] + ), + /* CO_APPLICANT */ + new CoBorrowerPersonalInfoPage('PAGETITLES.COMMON.COAPP_PERSONAL_INFO', + 'co-applicant-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new CoBorrowerAddressInfoPage('PAGETITLES.COMMON.COAPP_ADDRESS_INFO', + 'co-applicant-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new CoBorrowerEmploymentInfoPage('PAGETITLES.COMMON.COAPP_EMPLOYMENT_INFO', + 'co-applicant-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ]), + /* CO_APPLICANT_SPOUSE */ + new SpouseTypeInfoPage('PAGETITLES.COMMON.COAPP_SPOUSETYPE', + 'co-applicant-spouse-type', + [ + CommonGroups.IS_FULL_INFO_SPOUSE + ] + ), + new CoBorrowerTattleSpouseInfoPage('PAGETITLES.COMMON.COAPP_TATTLESPOUSE', + 'co-applicant-spouse', + [ + CommonGroups.TATTLE_SPOUSE_PERSONAL_INFO + ] + ), + new CoBorrowerSpousePersonalInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_PERSONAL_INFO', + 'co-applicant-spouse-personal', + [ + CommonGroups.PERSONAL_INFO + ] + ), + new CoBorrowerSpouseAddressInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_ADDRESS_INFO', + 'co-applicant-spouse-residential', + [ + CommonGroups.ADDRESS_INFO + ] + ), + new CoBorrowerSpouseEmploymentInfoPage('PAGETITLES.COMMON.COAPPSPOUSE_EMPLOYMENT_INFO', + 'co-applicant-spouse-financial', + [ + CommonGroups.EMPLOYMENT_INFO, + CommonGroups.INCOME_INFO, + CommonGroups.ALIMONY_INFO + ] + ), + new VehicleCollateralInfoPage('PAGETITLES.VEHICLE.COLLATERAL', 've-collateral', + [ + CommonGroups.VEHICLE_COLLATERAL_INFO_PARTIAL + ] + ), + new CreditCardDisclosuresInfoPage('PAGETITLES.COMMON.DISCLOSURES', 'cc-disclosures', + [ + CommonGroups.CC_DISCLOSURE_INFO // TODO: Make a different disclosure group, or change the name. + ] + ), + new SummaryInfoPage('PAGETITLES.COMMON.SUMMARY', 'cc-summary', []), + new ProcessingPage('PAGETITLES.CC.PROCESS', 'process', []), + new DecisionPage('PAGETITLES.CC.INTRO', 'decision', []) + +]; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-routing.module.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-routing.module.ts new file mode 100644 index 0000000..0543086 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/loan-wizard/wizards/vehicle/vehicle-routing.module.ts @@ -0,0 +1,66 @@ +import { Route} from '@angular/router'; +import { PersonalInfoPageComponent } from '../common/personal/personal-info-page.component'; +import { AddressInfoPageComponent } from '../common/address/address-info-page.component'; +import { EmploymentInfoPageComponent } from '../common/employment/employment-info-page.component'; +import { CreditCardDisclosuresPageComponent } from '../credit-card/credit-card-disclosures-page.component'; +import { SummaryPageComponent } from '../common/summary/summary-page.component'; +import { DecisionPageComponent } from '../common/decision/decision-page.component'; +import { LoanRouteGuard } from '../../../shared/guards/loan-route.guard'; +import { BorrowerType } from '../../../shared/borrower-type'; +import { TattleSpouseInfoPageComponent } from '../common/tattle-spouse/tattle-spouse-info-page.component'; +import { SpouseTypeInfoPageComponent } from '../common/spouse-type/spouse-type-info-page.component'; +import { ProcessingPageComponent } from '../common/processing/processing-page.component'; +import { EquityMemberInfoPageComponent } from '../equity/equity-member-info-page.component'; +import { VehicleCollateralInfoPageComponent } from './vehicle-collateral-info-page.component'; +import { StepperControllerGuard } from 'src/app/shared/guards/stepper-controller-guard'; +import { MemberCoreResolver } from 'src/app/member/member-core.resolver'; +import { environment } from 'src/environments/environment'; + + +export const veRoutes: Route = { + path: 've/:type', + resolve: environment.coreResolver ? { CoreIndividual: MemberCoreResolver} : {}, + canActivate: [StepperControllerGuard], + canActivateChild: [LoanRouteGuard], + children: [ + { path: 'step-1', component: EquityMemberInfoPageComponent }, + { path: 'step-2', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'step-3', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'step-4', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'applicant-spouse-type', component: SpouseTypeInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER}] }, + { path: 'applicant-spouse', component: TattleSpouseInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'applicant-spouse-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.PRIMARY_BORROWER_SPOUSE}] }, + { path: 'co-applicant-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-spouse-type', component: SpouseTypeInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER}] }, + { path: 'co-applicant-spouse', component: TattleSpouseInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-personal', component: PersonalInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-residential', component: AddressInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 'co-applicant-spouse-financial', component: EmploymentInfoPageComponent, + data: [{individual: BorrowerType.CO_BORROWER_SPOUSE}] }, + { path: 've-collateral', component: VehicleCollateralInfoPageComponent }, + { path: 'cc-disclosures', component: CreditCardDisclosuresPageComponent }, + { path: 'cc-summary', component: SummaryPageComponent }, + { path: 'process', component: ProcessingPageComponent }, + { path: 'decision', component: DecisionPageComponent }, + { path: '', redirectTo: 'step-1', pathMatch: 'prefix' } + ] +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-core.resolver.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-core.resolver.ts new file mode 100644 index 0000000..cf653c0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-core.resolver.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { Resolve } from '@angular/router'; +import { MemberService } from './member.service'; +import { CoreIndividual } from './models/core-individual'; + +@Injectable() +export class MemberCoreResolver implements Resolve { + + constructor(private memberService: MemberService) {} + + public resolve(): Observable | boolean { + if (this.memberService.shouldLoadIndividualData()) { + const isDoneResolvingSubject = new Subject(); + this.memberService.requestIndividual() + .subscribe( + (individual: CoreIndividual) => { + this.memberService.transformCoreData(individual); + isDoneResolvingSubject.next(true); + isDoneResolvingSubject.complete(); + }, + (failure) => { + isDoneResolvingSubject.next(true); + isDoneResolvingSubject.complete(); + } + ); + return isDoneResolvingSubject.asObservable(); + } + return true; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.html new file mode 100644 index 0000000..154882e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.html @@ -0,0 +1,54 @@ +
+
+
+ +
MEMBER.LOGIN.MEMBER_SUBTEXT
+
+
+
+ +
+
+
+ +
+ +
+ +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+ +
{{localState.errorMessage | translate}}
+
+
+
+
+
+
+ +
MEMBER.LOGIN.NON_MEMBER_SUBTEXT
+
+ +
+
+
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.scss new file mode 100644 index 0000000..9a32f71 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.scss @@ -0,0 +1,51 @@ +.card { + background-color: #F8F9FA; +} + +.btn-success { + margin-left: 15px; + margin-bottom: 20px; +} + +.btn-disabled { + margin-bottom: 20px; + margin-left: 15px; +} + +.btn-link { + border: 0px; + padding-left: 15px; +} + +.login-header { + font-family: 'Open Sans', sans-serif !important; + margin-left:15px; + margin-bottom:5px; + margin-top: 15px; + font-size: 32px; + font-weight: bold; + color: #005674; +} + +.middle-text { + font-family: Arial, sans-serif !important; + margin-left:15px; + margin-bottom: 15px; +} + +.error-message { + font-size: 80%; + color: #dc3545; + font-style: italic; + margin-left: 15px; +} + +@media (max-width: 767px) { + .card-body { + padding: 0px; + } + + .login-header { + font-size: 24px; + } +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.spec.ts new file mode 100644 index 0000000..2909445 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.spec.ts @@ -0,0 +1,145 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { MemberLoginComponent } from './member-login.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { of } from 'rxjs'; +import { MemberService } from '../member.service'; +import { CookieUtil } from 'src/app/shared/cookie/cookie-util'; +import { LoginStatus } from '../models/login-status'; + +describe('MemberLoginComponent', () => { + let component: MemberLoginComponent; + let service: MemberService; + let fixture: ComponentFixture; + + const mockSuccessStatus: LoginStatus = { + statusMessage : 'success', + errorInfo : null + }; + + const mockInvalidCredStatus: LoginStatus = { + statusMessage : 'failure', + errorInfo : { + errorCode : '25002', + errorMessage : 'Fake error message', + errorType : 'APP_ERROR' + } + }; + + const mockLockedStatus: LoginStatus = { + statusMessage : 'failure', + errorInfo : { + errorCode : '25100', + errorMessage : 'Fake error message', + errorType : 'APP_ERROR' + } + }; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + MemberLoginComponent, + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MemberLoginComponent); + service = TestBed.get(MemberService); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should intilize local state with default values', () => { + fixture.detectChanges(); + expect(component.localState.error).toBe(false); + expect(component.localState.loginDisabled).toBe(true); + expect(component.localState.errorMessage).toBe(''); + }); + + it('should authenticate member and throw error when user credentials are incorrect', () => { + fixture.detectChanges(); + spyOn(service, 'authenticateMember').and.returnValue(of(mockInvalidCredStatus)); + component.commandLogin(); + expect(service.authenticateMember).toHaveBeenCalled(); + expect(component.localState.errorMessage).toBe('MEMBER.LOGIN.ERROR_USER'); + }); + + it('should authenticate member and throw error when user is locked', () => { + fixture.detectChanges(); + spyOn(service, 'authenticateMember').and.returnValue(of(mockLockedStatus)); + component.commandLogin(); + expect(service.authenticateMember).toHaveBeenCalled(); + expect(component.localState.errorMessage).toBe('MEMBER.LOGIN.ERROR_LOCKED'); + }); + + it('should authenticate member and NOT throw error when authentication passes', () => { + fixture.detectChanges(); + spyOn(service, 'authenticateMember').and.returnValue(of(mockSuccessStatus)); + spyOn(CookieUtil, 'hasAuthCookie').and.returnValue(true); + spyOn(component, 'routeLoanApplication'); + component.commandLogin(); + expect(service.authenticateMember).toHaveBeenCalled(); + expect(component.routeLoanApplication).toHaveBeenCalled(); + expect(component.localState.errorMessage).toBe(''); + }); + + it('should authenticate non member and route when cookies exist', () => { + fixture.detectChanges(); + spyOn(service, 'authenticateNonMember').and.returnValue(of(mockSuccessStatus)); + spyOn(CookieUtil, 'hasAuthCookie').and.returnValue(true); + spyOn(component, 'routeLoanApplication'); + spyOn(component, 'routeLoanApplicationNoCookies'); + component.commandNonMember(); + expect(service.authenticateNonMember).toHaveBeenCalled(); + expect(component.routeLoanApplication).toHaveBeenCalled(); + expect(component.routeLoanApplicationNoCookies).not.toHaveBeenCalled(); + }); + + it('should authenticate non member and route when cookies dont exist', () => { + fixture.detectChanges(); + spyOn(service, 'authenticateNonMember').and.returnValue(of(mockSuccessStatus)); + spyOn(CookieUtil, 'hasAuthCookie').and.returnValue(false); + spyOn(component, 'routeLoanApplication'); + spyOn(component, 'routeLoanApplicationNoCookies'); + component.commandNonMember(); + expect(service.authenticateNonMember).toHaveBeenCalled(); + expect(component.routeLoanApplication).not.toHaveBeenCalled(); + expect(component.routeLoanApplicationNoCookies).toHaveBeenCalled(); + }); + + it('should route user to landmark login page', () => { + fixture.detectChanges(); + expect(component.localState.existingAppIndicator).toBe(false); + spyOn(component, 'routeToLandmarkLogin'); + component.commandExistingApp(); + expect(component.routeToLandmarkLogin).toHaveBeenCalled(); + expect(component.localState.existingAppIndicator).toBe(true); + }); + + it('should throw errors and reset password fields', () => { + fixture.detectChanges(); + spyOn(component, 'resetPasswordField'); + component.throwError('This is a test error'); + expect(component.resetPasswordField).toHaveBeenCalled(); + expect(component.localState.errorMessage).toBe('This is a test error'); + expect(component.localState.error).toBe(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.ts new file mode 100644 index 0000000..68c2d14 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.component.ts @@ -0,0 +1,142 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { ActivatedRoute, Router } from '@angular/router'; +import { MemberLoginService } from './member-login.service'; +import { MemberService } from '../member.service'; +import { CookieUtil } from 'src/app/shared/cookie/cookie-util'; +import { LoginStatus } from '../models/login-status'; +import { environment } from 'src/environments/environment'; + +@Component({ + selector: 'lw-member-login', + templateUrl: './member-login.component.html', + styleUrls: ['./member-login.component.scss'] +}) +export class MemberLoginComponent implements OnInit, OnDestroy { + private redirect; + public pageForm: FormGroup; + public loginGroup: FormGroup; + public statusSubscription: Subscription; + public readonly FORM_STATE_INVALID = 'INVALID'; + public localState = { + loginIndicator: false, + nonMemberIndicator: false, + existingAppIndicator: false, + loginDisabled: true, + error: false, + errorMessage: '' + }; + + constructor(private router: Router, private memberLoginService: MemberLoginService, + private memberService: MemberService, private route: ActivatedRoute) { } + + ngOnInit () { + this.getUrlParams(); + this.setupForm(); + this.setupLocalState(); + } + + ngOnDestroy () { + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public getUrlParams(): void { + this.route.queryParams.subscribe(params => this.redirect = params['redirect'] || '/select-loan'); + } + + public setupForm(): void { + this.pageForm = this.memberLoginService.createPageGroup(); + this.loginGroup = this.pageForm.get(this.memberLoginService.LOGIN_FORM_NAME); + } + + public setupLocalState(): void { + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.loginDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + } + + public commandLogin(): void { + this.localState.loginDisabled = true; + this.localState.loginIndicator = true; + this.memberService.authenticateMember(this.loginGroup.value.username, this.loginGroup.value.password).subscribe ( + (success: LoginStatus) => { + if (success && success.errorInfo) { + this.checkErrorCode(success.errorInfo.errorCode); + } else { + if (CookieUtil.hasAuthCookie()) { + this.routeLoanApplication(); + } else { + this.throwError('MEMBER.LOGIN.ERROR_COOKIES'); + } + } + }, (error) => { + this.throwError('MEMBER.LOGIN.ERROR_SERVER'); + } + ); + } + + public checkErrorCode(errorCode: string): void { + if (errorCode === '25002') { + this.throwError('MEMBER.LOGIN.ERROR_USER'); + } else if (errorCode === '25100') { + this.throwError('MEMBER.LOGIN.ERROR_LOCKED'); + } else { + this.throwError('MEMBER.LOGIN.ERROR_USER'); + throw new Error(`Unexpected login error code: ${errorCode}`); + } + } + + public commandNonMember(): void { + this.localState.loginDisabled = true; + this.localState.nonMemberIndicator = true; + this.memberService.authenticateNonMember().subscribe ( + (success) => { + if (CookieUtil.hasAuthCookie()) { + this.routeLoanApplication(); + } else { + this.routeLoanApplicationNoCookies(); + } + }, (error) => { + this.routeLoanApplicationNoCookies(); + } + ); + } + + public commandExistingApp(): void { + this.localState.existingAppIndicator = true; + this.routeToLandmarkLogin(); + } + + public routeToLandmarkLogin(): void { + location.href = environment.obsUri; + } + + public routeLoanApplication(): void { + this.router.navigateByUrl(this.redirect); + } + + public routeLoanApplicationNoCookies(): void { + this.router.navigate([this.redirect], { + queryParams: { + noCookie: true, + } + }); + } + + public resetPasswordField(): void { + this.loginGroup.patchValue({password: null}); + this.loginGroup.markAsPristine(); + this.loginGroup.markAsUntouched(); + } + + public throwError(error: string): void { + this.localState.loginIndicator = false; + this.resetPasswordField(); + this.localState.errorMessage = error; + this.localState.error = true; + } +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.service.spec.ts new file mode 100644 index 0000000..8efa2d5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.service.spec.ts @@ -0,0 +1,49 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { MemberLoginService } from './member-login.service'; +import { CommonGroups } from 'src/app/loan-wizard/field-groups/common-groups'; + + +describe('MemberLoginService', () => { + + let service: MemberLoginService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + MemberLoginService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(MemberLoginService); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create a login group', () => { + const loginGroup = service.createLoginGroup(); + expect(loginGroup).toBeTruthy(); + CommonGroups.LOGIN.fields.forEach((eachField) => { + expect(loginGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.service.ts new file mode 100644 index 0000000..9ba8364 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-login/member-login.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { CommonGroups } from 'src/app/loan-wizard/field-groups/common-groups'; + +@Injectable({ + providedIn: 'root' +}) +export class MemberLoginService { + + public readonly LOGIN_FORM_NAME = 'loginInformation'; + + constructor(private formBuilder: FormBuilder) { + } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + const loginGroup = this.createLoginGroup(); + group.addControl(this.LOGIN_FORM_NAME, loginGroup); + return group; + } + + public createLoginGroup(): FormGroup { + const loginGroup = this.formBuilder.group({}); + + CommonGroups.LOGIN.fields.forEach((eachField) => { + loginGroup.addControl(eachField.field, this.formBuilder.control('')); + loginGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + return loginGroup; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.html new file mode 100644 index 0000000..90c6b64 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.spec.ts new file mode 100644 index 0000000..600d7ad --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.spec.ts @@ -0,0 +1,36 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { MemberOutletComponent } from './member-outlet.component'; +import { HttpClient } from '@angular/common/http'; + +describe('MemberOutletComponent', () => { + let component: MemberOutletComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + ], + declarations: [ + MemberOutletComponent, + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MemberOutletComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.ts new file mode 100644 index 0000000..887ea46 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-outlet/member-outlet.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-member-outlet', + templateUrl: './member-outlet.component.html', + styleUrls: ['./member-outlet.component.scss'] +}) +export class MemberOutletComponent { + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.html new file mode 100644 index 0000000..f160299 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.html @@ -0,0 +1,56 @@ +
+
+
+ +
MEMBER.VERIFICATION.SUBTEXT
+
+
+ +
+ +
+
+
+ +
+ +
+ + + +
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+ +
+ + + + + +
{{localState.errorMessage | translate}} attempt {{attempts.value}} of {{MAX_ATTEMPTS}}.
+
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.scss new file mode 100644 index 0000000..8f514f5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.scss @@ -0,0 +1,36 @@ +.card { + background-color: #F8F9FA; +} + +.btn-success { + margin-left: 15px; + margin-bottom: 15px; +} + +.btn-link { + border: 0px; + padding-left: 15px; +} + +.login-header { + font-family: 'Open Sans', sans-serif !important; + margin-left:15px; + margin-bottom:5px; + margin-top: 15px; + font-size: 32px; + font-weight: bold; + color: #005674; +} + +.middle-text { + font-family: Arial, sans-serif !important; + margin-left:15px; + margin-bottom: 15px; +} + +.error-message { + font-size: 80%; + color: #dc3545; + font-style: italic; + margin-left: 15px; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.spec.ts new file mode 100644 index 0000000..1c824ad --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.spec.ts @@ -0,0 +1,100 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { MemberService } from '../member.service'; +import { MemberVerificationComponent } from './member-verification.component'; +import { of } from 'rxjs'; +import { Router } from '@angular/router'; + +describe('MemberVerificationComponent', () => { + let component: MemberVerificationComponent; + let service: MemberService; + let fixture: ComponentFixture; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + declarations: [ + MemberVerificationComponent, + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MemberVerificationComponent); + service = TestBed.get(MemberService); + component = fixture.componentInstance; + router = TestBed.get(Router); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should intilize local state with default values', () => { + fixture.detectChanges(); + expect(component.localState.loginIndicator).toBe(false); + expect(component.localState.loginDisabled).toBe(true); + expect(component.localState.hideSSN).toBe(true); + expect(component.localState.errorMessage).toBe(''); + }); + + it('should identify individual, set individual id, and route upon success', () => { + fixture.detectChanges(); + spyOn(service, 'identifyIndividual').and.returnValue(of('1234A')); + spyOn(router, 'navigateByUrl').and.returnValue( Promise.resolve(true)); + component.commandContinue(); + expect(service.identifyIndividual).toHaveBeenCalled(); + expect(router.navigateByUrl).toHaveBeenCalledWith('/select-loan'); + }); + + it('should route without verification', () => { + fixture.detectChanges(); + expect(component.localState.noVerifyIndicator).toBe(false); + spyOn(component, 'routeLoanApplicationNoCookies'); + spyOn(service, 'authenticateNonMember').and.returnValues( of(true)); + component.commandContinueWithoutVerification(); + expect(component.localState.noVerifyIndicator).toBe(true); + expect(component.routeLoanApplicationNoCookies).toHaveBeenCalled(); + }); + + it('should subscribe to attempts and throw error if attempts exceeds max attempts', () => { + fixture.detectChanges(); + spyOn(component, 'getAttemptsAsObservable').and.returnValue(of(10)); + component.subscribeToAttempts(); + expect(component.localState.loginDisabled).toBe(true); + expect(component.localState.showNoVerifyButton).toBe(true); + expect(component.localState.errorMessage).toBe('MEMBER.VERIFICATION.MAX_ATTEMPTS'); + expect(component.localState.error).toBe(true); + }); + + it('should throw error', () => { + fixture.detectChanges(); + spyOn(component, 'setAttempts'); + component.throwError('This is a test error'); + expect(component.localState.errorMessage).toBe('This is a test error'); + expect(component.localState.error).toBe(true); + expect(component.setAttempts).toHaveBeenCalled(); + }); + + it('should toggle hideSSN state', () => { + fixture.detectChanges(); + expect(component.localState.hideSSN).toBe(true); + component.commandToggleSSNView(); + expect(component.localState.hideSSN).toBe(false); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.ts new file mode 100644 index 0000000..708254e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.component.ts @@ -0,0 +1,155 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { Subscription, BehaviorSubject, Observable } from 'rxjs'; +import { ActivatedRoute, Router } from '@angular/router'; +import { MemberVerificationService } from './member-verification.service'; +import { MaskingService } from 'src/app/shared/masking/masking.service'; +import { MemberService } from '../member.service'; +import { CookieUtil } from '../../shared/cookie/cookie-util'; +import { finalize } from 'rxjs/operators'; + + +@Component({ + selector: 'lw-member-verification', + templateUrl: './member-verification.component.html', + styleUrls: ['./member-verification.component.scss'] +}) +export class MemberVerificationComponent implements OnInit, OnDestroy { + public attempts: BehaviorSubject = new BehaviorSubject(0); + private redirect = '/select-loan'; + public pageForm: FormGroup; + public verificationGroup: FormGroup; + public statusSubscription: Subscription; + public readonly FORM_STATE_INVALID = 'INVALID'; + public dobMask = MaskingService.dobMask; + public readonly MAX_ATTEMPTS = 3; + public localState = { + hideSSN: true, + loginIndicator: false, + loginDisabled: true, + showNoVerifyButton: false, + noVerifyIndicator: false, + error: false, + errorMessage: '' + }; + + constructor(private router: Router, private memberVerificationService: MemberVerificationService, private memberService: MemberService, + private route: ActivatedRoute) { } + + ngOnInit() { + // this.getUrlParams(); + this.setupForm(); + this.setupLocalState(); + this.subscribeToAttempts(); + } + + ngOnDestroy() { + if (this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public getUrlParams(): void { + this.route.queryParams.subscribe((params) => { + this.redirect = params['redirect'] || '/select-loan'; + }); + } + + public setupForm(): void { + this.pageForm = this.memberVerificationService.createPageGroup(); + this.verificationGroup = this.pageForm.get(this.memberVerificationService.VERIFICATION_FORM_NAME); + } + + public setupLocalState(): void { + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.loginDisabled = (newStatus === this.FORM_STATE_INVALID); + }); + } + + public subscribeToAttempts(): void { + this.getAttemptsAsObservable().subscribe((attempts) => { + if (attempts >= this.MAX_ATTEMPTS) { + this.localState.loginDisabled = true; + this.localState.showNoVerifyButton = true; + this.localState.errorMessage = 'MEMBER.VERIFICATION.MAX_ATTEMPTS'; + this.localState.error = true; + } + }); + } + + public commandContinue(): void { + this.localState.loginDisabled = true; + this.localState.loginIndicator = true; + this.memberService.identifyIndividual(this.verificationGroup.value.dob, this.verificationGroup.value.ssn).pipe( + finalize(() => { + this.localState.loginDisabled = false; + this.localState.loginIndicator = false; + }) + ).subscribe( + () => { + const promise = this.router.navigateByUrl(this.redirect); + promise.then((result) => { + if (result === false) { + this.throwError('MEMBER.VERIFICATION.FAILURE'); + } + }); + promise.catch((reason) => { + // @TODO NOTIFY_ERROR + this.throwError('MEMBER.VERIFICATION.FAILURE'); + }); + }, (error) => { + this.throwError('MEMBER.VERIFICATION.ERROR'); + } + ); + } + + public commandContinueWithoutVerification() { + this.localState.noVerifyIndicator = true; + this.memberService.authenticateNonMember().subscribe( + (success) => { + if (CookieUtil.hasAuthCookie()) { + this.routeLoanApplication(); + } else { + this.routeLoanApplicationNoCookies(); + } + }, (error) => { + this.routeLoanApplicationNoCookies(); + } + ); + } + + public commandToggleSSNView(): void { + this.localState.hideSSN = !this.localState.hideSSN; + } + + public routeLoanApplication(): void { + this.router.navigateByUrl(this.redirect); + } + + public routeLoanApplicationNoCookies(): void { + this.router.navigate([this.redirect], { + queryParams: { + noCookie: true, + } + }); + } + + public setAttempts(attempts: number): void { + this.attempts.next(attempts); + } + + public getAttempts(): number { + return this.attempts.value; + } + + public getAttemptsAsObservable(): Observable { + return this.attempts.asObservable(); + } + + public throwError(error: string): void { + this.localState.errorMessage = error; + this.localState.error = true; + this.setAttempts(this.getAttempts() + 1); + } +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.service.spec.ts new file mode 100644 index 0000000..41e4d56 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.service.spec.ts @@ -0,0 +1,51 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { CommonGroups } from 'src/app/loan-wizard/field-groups/common-groups'; +import { MemberVerificationService } from './member-verification.service'; + + +describe('MemberVerificationService', () => { + + let service: MemberVerificationService; + let httpTestingController: HttpTestingController; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + MemberVerificationService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(MemberVerificationService); + httpTestingController = TestBed.get(HttpTestingController); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create a login group', () => { + const verificationGroup = service.createVerificationGroup(); + expect(verificationGroup).toBeTruthy(); + CommonGroups.MEMBER_VERIFICATION.fields.forEach((eachField) => { + expect(verificationGroup.get(eachField.field)).toBeTruthy(); + }, this); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.service.ts new file mode 100644 index 0000000..8348097 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member-verification/member-verification.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { CommonGroups } from 'src/app/loan-wizard/field-groups/common-groups'; +import { of, Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class MemberVerificationService { + public readonly VERIFICATION_FORM_NAME = 'memberInformation'; + + constructor(private formBuilder: FormBuilder) { + } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + const verificationGroup = this.createVerificationGroup(); + group.addControl(this.VERIFICATION_FORM_NAME, verificationGroup); + return group; + } + + public createVerificationGroup(): FormGroup { + const verificationGroup = this.formBuilder.group({}); + + CommonGroups.MEMBER_VERIFICATION.fields.forEach((eachField) => { + verificationGroup.addControl(eachField.field, this.formBuilder.control('')); + verificationGroup.controls[eachField.field].setValidators(eachField.validation.slice()); + }, this); + + return verificationGroup; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member.service.spec.ts new file mode 100644 index 0000000..09aee1e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member.service.spec.ts @@ -0,0 +1,107 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { MemberService } from './member.service'; +import { environment } from 'src/environments/environment'; +import { CookieUtil } from '../shared/cookie/cookie-util'; +import { CreditApplicationService } from '../shared/credit-application.service'; + +describe('Member Service', () => { + + let service: MemberService; + let creditApplicationService: CreditApplicationService; + let httpTestingController: HttpTestingController; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + MemberService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(MemberService); + creditApplicationService = TestBed.get(CreditApplicationService); + httpTestingController = TestBed.get(HttpTestingController); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should build authentication url', () => { + const url = service.buildAuthenticateURL(); + expect(url).toBe(environment.authUrlBase + '/SAMLAssertionConsumer/Authenticate'); + }); + + it('should build initsession url', () => { + const url = service.buildInitSessionURL(); + expect(url).toBe(environment.authUrlBase + '/SAMLAssertionConsumer/InitSession'); + }); + + it('should build member meta url', () => { + const url = service.buildMemberMetaURL(); + expect(url).toBe(environment.authUrlBase + '/SAMLAssertionConsumer/MemberInfo'); + }); + + it('should build identify url', () => { + const url = service.buildIdentifyURL(); + expect(url).toBe(environment.authUrlBase + '/SAMLAssertionConsumer/Identify'); + }); + + it('should build individual url', () => { + const url = service.buildIndividualURL(); + expect(url).toBe(environment.urlBase + '/Individual/self'); + }); + + it('should authenticate member', () => { + service.authenticateMember('peter', 'pan').subscribe(() => {}); + const req = httpTestingController.expectOne(service.buildAuthenticateURL()); + expect(req.request.method).toEqual('POST'); + httpTestingController.verify(); + }); + + it('should authenticate non member', () => { + service.authenticateNonMember().subscribe(() => {}); + const req = httpTestingController.expectOne(service.buildInitSessionURL()); + expect(req.request.method).toEqual('GET'); + httpTestingController.verify(); + }); + + it('should identify individual', () => { + service.identifyIndividual('12/12/1999', '2341').subscribe(() => {}); + const req = httpTestingController.expectOne(service.buildIdentifyURL()); + expect(req.request.method).toEqual('POST'); + httpTestingController.verify(); + }); + + it('should request individual', () => { + service.requestIndividual().subscribe(() => {}); + const req = httpTestingController.expectOne(service.buildIndividualURL()); + expect(req.request.method).toEqual('GET'); + httpTestingController.verify(); + }); + + it('should not load individual data if it is already filled', () => { + spyOn(CookieUtil, 'hasAuthCookie').and.returnValue(true); + spyOn(CookieUtil, 'allowPrefill').and.returnValue(true); + spyOn(creditApplicationService, 'canAcceptIndividualPrefill').and.returnValue(false); + expect(service.shouldLoadIndividualData()).toEqual(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/member.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member.service.ts new file mode 100644 index 0000000..f32d566 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/member.service.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { environment } from 'src/environments/environment'; +import { HttpClient } from '@angular/common/http'; +import { CreditApplicationService } from '../shared/credit-application.service'; +import { MemberCredentials } from './models/member-credentials'; +import { MemberMeta } from './models/member-meta'; +import { CookieUtil } from '../shared/cookie/cookie-util'; +import { IndividualCredentials } from './models/individual-credentials'; +import { CoreIndividual } from './models/core-individual'; +import { CoreTranslatorService } from '../shared/core-translator/core-translator.service'; +import { map } from 'rxjs/operators'; + + +@Injectable({ + providedIn: 'root' +}) +export class MemberService { + constructor(private httpClient: HttpClient, private creditApplicationService: CreditApplicationService, + private coreTranslatorService: CoreTranslatorService) { + } + + public buildAuthenticateURL(): string { + return environment.authUrlBase + '/SAMLAssertionConsumer/Authenticate'; + } + + public buildInitSessionURL(): string { + return environment.authUrlBase + '/SAMLAssertionConsumer/InitSession'; + } + + public buildMemberMetaURL(): string { + return environment.authUrlBase + '/SAMLAssertionConsumer/MemberInfo'; + } + + public buildIdentifyURL(): string { + return environment.authUrlBase + '/SAMLAssertionConsumer/Identify'; + } + + public buildIndividualURL(): string { + return environment.urlBase + '/Individual/self'; + } + + public authenticateMember(username: string, password: string): Observable { + const memberCredentials: MemberCredentials = new MemberCredentials(username, password); + return this.httpClient.post(this.buildAuthenticateURL(), memberCredentials, {withCredentials: true}); + } + + public authenticateNonMember(): Observable { + return this.httpClient.get(this.buildInitSessionURL(), {withCredentials: true}); + } + + // make a call to core to get individual on account. return individual id if successful. + public identifyIndividual(dob: string, ssn: string): Observable { + const individualCredentials: IndividualCredentials = new IndividualCredentials(dob, ssn); + return this.httpClient.post(this.buildIdentifyURL(), individualCredentials, {withCredentials: true}); + } + + // make a call to core to retrieve user data. + public requestIndividual(): Observable { + return this.httpClient.get(this.buildIndividualURL(), {withCredentials: true}) + .pipe(map((individualRaw: any) => { + return individualRaw; + })); + } + + public transformCoreData(individual: CoreIndividual): void { + const creditApplication = this.creditApplicationService.getFullCreditApplication(); + this.coreTranslatorService.setCoreData(creditApplication, individual); + } + + public shouldShowVerificationPage() { + return (CookieUtil.hasAuthCookie() && CookieUtil.requiresVerification()); + } + + public shouldLoadIndividualData() { + return (CookieUtil.hasAuthCookie() && CookieUtil.allowPrefill() && this.creditApplicationService.canAcceptIndividualPrefill() ); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-address.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-address.ts new file mode 100644 index 0000000..0535159 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-address.ts @@ -0,0 +1,21 @@ + + +export interface CoreAddress { + addressId: number; + line1: string; + line2: string; + line3: string; + city: string; + state: string; + zipCode: string; + country: string; + countyName: string; + addressType: string; + isPrimary: boolean; + isInternational: boolean; + addressVerification: number; + occupancy: string; + startDate: string; + endDate: string; + uspsVerified: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-individual.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-individual.ts new file mode 100644 index 0000000..140db6b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-individual.ts @@ -0,0 +1,38 @@ +import { CoreAddress } from './core-address'; +import { CorePhone } from './core-phone'; + +export interface CoreIndividual { + individualId: number; + individualType: string; + title: string; + firstName: string; // individual.personalInfo.firstName + middleName: string; // individual.personalInfo.middleInitial (take first initial) + lastName: string; // individual.personalInfo.lastName + nameSuffix: string; // individual.personalInfo.suffix + taxId: string; + gender: string; + birthDate: string; // individual.personalInfo.birthday + branch: number; + driversLicense: string; + driversLicenseState: string; + ident: string; + preferredContact: string; + maritalStatus: string; // individual.personalInfo.martialStatus + isUSCitizen: boolean; + primaryAddressId: number; + isEmployee: boolean; + employeeNumber: number; + companyName: string; + employerName: string; + applicationCountLast90Days: number; + applicationsDeclinedLast90Days: number; + applicationsApprovedLast90Days: number; + address: CoreAddress[]; + email1: string; // individual.personalInfo.email + email2: string; + phone: CorePhone[]; // individual.personalInfo.primaryPhoneNumberType, individual.personalInfo.primaryPhoneNumber + status: string; + isHomeOwner: boolean; + isHeadOfHouseHold: boolean; + language: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-phone.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-phone.ts new file mode 100644 index 0000000..c45e418 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/core-phone.ts @@ -0,0 +1,11 @@ + + +export class CorePhone { + public phoneKey: string; + public isPrimaryForType: boolean; + public isInternational: boolean; + public countryCode: string; + public areaCode: string; + public phoneNumber: string; + public extension: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/individual-credentials.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/individual-credentials.ts new file mode 100644 index 0000000..a212e22 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/individual-credentials.ts @@ -0,0 +1,10 @@ + +export class IndividualCredentials { + public dateOfBirth: string; + public socialSecurityNumber: string; + + constructor(dateOfBirth: string, socialSecurityNumber: string) { + this.dateOfBirth = dateOfBirth; + this.socialSecurityNumber = socialSecurityNumber; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/login-status.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/login-status.ts new file mode 100644 index 0000000..7560baf --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/login-status.ts @@ -0,0 +1,13 @@ + + +export class LoginStatus { + public statusMessage: string; + public errorInfo: ErrorInfo; +} + +export class ErrorInfo { + public errorCode: string; + public errorMessage: string; + public errorType: string; +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/member-credentials.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/member-credentials.ts new file mode 100644 index 0000000..0f40967 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/member-credentials.ts @@ -0,0 +1,10 @@ + +export class MemberCredentials { + public username: string; + public password: string; + + constructor(username: string, password: string) { + this.username = username; + this.password = password; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/member-meta.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/member-meta.ts new file mode 100644 index 0000000..bd7fe89 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/member/models/member-meta.ts @@ -0,0 +1,12 @@ + +export class MemberMeta { + public isTwoFactor: boolean; + public isMember: boolean; + public isIndividual: boolean; + + constructor() { + this.isTwoFactor = false; + this.isMember = false; + this.isIndividual = false; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/card.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/card.component.scss new file mode 100644 index 0000000..04dfe59 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/card.component.scss @@ -0,0 +1,102 @@ +//main-card +.main-card-text { + font-weight: bold; + font-size: 25px; +} +.main-card-footer { + margin-bottom: 10px; +} + +@media (max-width: 767px) { + .main-card-text { + float: left; + font-size: 17px; + } + .main-card-footer { + margin-bottom: 0px; + } +} + +//sub-card +.sub-card-text { + font-weight: bold; + font-size: 25px; + margin-top: 30px +} +.sub-card-footer { + margin-bottom: 30px; +} + +@media (max-width: 767px) { + .sub-card-text { + float: left; + font-size: 17px; + margin-top: 0px; + } + .sub-card-footer { + margin-bottom: 0px; + } +} + + +//shared +.card { + font-family: Arial, sans-serif; + margin-bottom: 25px; + text-decoration: none; + color:#565656; +} +.row { + text-align: center; + padding-right: 0px; + padding-left: 0px; +} +.card-body { + padding-right: 0px; + padding-left: 0px; +} +.main-icon { + margin-top: 10px; + margin-bottom: 8px; +} +.footer-text { + margin-right: 8px; +} +.desktop-view { + font-size: 20px; + margin-right: 8px; +} + +.fa-angle-right { + padding-top: 4px; +} + +/*mobile*/ +@media (max-width: 767px) { + .card { + margin-bottom: 10px; + } + .footer-text { + float: right; + } + .main-icon { + margin-top: 0px; + margin-bottom: 0px; + } + .desktop-view { + display: none; + } + .mobile-view[style] { + display: inline !important; + } + + .card-body { + padding-right: 15px; + padding-left: 15px; + } + + .fa-angle-right { + padding-top: 0px; + } + +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.html new file mode 100644 index 0000000..773f73c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.html @@ -0,0 +1,30 @@ + +
+
+
+
+ + + + +
+
+
+
{{cardName}}
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.scss new file mode 100644 index 0000000..afdf01d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.scss @@ -0,0 +1 @@ +// see card-component.scss for styling \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.spec.ts new file mode 100644 index 0000000..84b4a22 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.spec.ts @@ -0,0 +1,45 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { MainCardComponent } from './main-card.component'; + +describe('MainCardComponent', () => { + let component: MainCardComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + MainCardComponent, + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MainCardComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should create with input', () => { + fixture.detectChanges(); + component.desktopIcon = 'fas fa-credit-card fa-3x'; + component.cardName = 'Credit Card'; + component.cardPath = 'credit-card'; + fixture.detectChanges(); + const icon = fixture.nativeElement.querySelectorAll('.fa-credit-card'); + expect(icon).toBeDefined(); + expect(icon.length).toBeGreaterThanOrEqual(1); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.ts new file mode 100644 index 0000000..44957f9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/main-card/main-card.component.ts @@ -0,0 +1,20 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'lw-main-card', + templateUrl: './main-card.component.html', + styleUrls: ['../card.component.scss'] +}) +export class MainCardComponent { + @Input() + public cardPath: string; + @Input() + public desktopIcon: string; + @Input() + public mobileIcon: string; + @Input() + public cardName: string; + @Input() + public footerName: string; + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.html new file mode 100644 index 0000000..d295a21 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.html @@ -0,0 +1,20 @@ + +
+
+
+
{{cardName}}
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.scss new file mode 100644 index 0000000..afdf01d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.scss @@ -0,0 +1 @@ +// see card-component.scss for styling \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.spec.ts new file mode 100644 index 0000000..b647041 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { SubCardComponent } from './sub-card.component'; + +describe('SubCardComponent', () => { + let component: SubCardComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + SubCardComponent, + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SubCardComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.ts new file mode 100644 index 0000000..f4afd04 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/cards/sub-card/sub-card.component.ts @@ -0,0 +1,16 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'lw-sub-card', + templateUrl: './sub-card.component.html', + styleUrls: ['../card.component.scss'] +}) +export class SubCardComponent { + @Input() + public cardPath: string; + @Input() + public cardName: string; + @Input() + public footerName: string; + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.html new file mode 100644 index 0000000..59eee6d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.html @@ -0,0 +1,21 @@ +
LANDING.CREDIT_CARD
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.spec.ts new file mode 100644 index 0000000..cea3918 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.spec.ts @@ -0,0 +1,37 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { SelectCreditCardComponent } from './select-credit-card.component'; +import { configureTestSuite } from 'ng-bullet'; +import { SubCardComponent } from '../cards/sub-card/sub-card.component'; + +describe('SelectCreditCardComponent', () => { + let component: SelectCreditCardComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + SelectCreditCardComponent, + SubCardComponent + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectCreditCardComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.ts new file mode 100644 index 0000000..419309d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/credit-card/select-credit-card.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-select-credit-card', + templateUrl: './select-credit-card.component.html', + styleUrls: ['../select-loan.component.scss'] +}) + +export class SelectCreditCardComponent { + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.html new file mode 100644 index 0000000..107f16c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.html @@ -0,0 +1,25 @@ +
LANDING.HOME_EQUITY
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.spec.ts new file mode 100644 index 0000000..6d5646e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.spec.ts @@ -0,0 +1,37 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { SelectHomeEquityComponent } from './select-home-equity.component'; +import { SubCardComponent } from '../cards/sub-card/sub-card.component'; + +describe('SelectHomeEquityComponent', () => { + let component: SelectHomeEquityComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + SelectHomeEquityComponent, + SubCardComponent + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectHomeEquityComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.ts new file mode 100644 index 0000000..b34c82c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/home/select-home-equity.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-select-home-equity', + templateUrl: './select-home-equity.component.html', + styleUrls: ['../select-loan.component.scss'] +}) + +export class SelectHomeEquityComponent { + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.html new file mode 100644 index 0000000..90c6b64 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.spec.ts new file mode 100644 index 0000000..2d0fb66 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.spec.ts @@ -0,0 +1,37 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { SelectLoanComponent } from '../select-loan.component'; +import { configureTestSuite } from 'ng-bullet'; +import { SelectLoanOutletComponent } from './select-loan-outlet.component'; +import { SubCardComponent } from '../cards/sub-card/sub-card.component'; + +describe('SelectLoanOutletComponent', () => { + let component: SelectLoanOutletComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + ], + declarations: [ + SelectLoanOutletComponent, + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectLoanOutletComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.ts new file mode 100644 index 0000000..58630e3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/outlet/select-loan-outlet.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-select-loan-outlet', + templateUrl: './select-loan-outlet.component.html', + styleUrls: ['../select-loan.component.scss'] +}) +export class SelectLoanOutletComponent { + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.html new file mode 100644 index 0000000..c58cdbc --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.html @@ -0,0 +1,24 @@ +
+
LANDING.TITLE
+
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.scss new file mode 100644 index 0000000..a243617 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.scss @@ -0,0 +1,35 @@ +.back-text { + font-family: Arial, sans-serif; + padding-left: 15px; + color: #3A383B; + text-decoration: underline; +} + +.loan-header { + font-family: 'Open Sans', sans-serif !important; + font-size:44px; + color:#012E50 +} + +.title-hr { + margin: 0 0 2.5em 0; + color: #B1B1B1; + height: 1px; + border: none; + background-color: #B1B1B1; +} + +/*mobile*/ +@media (max-width: 767px) { + .back-text { + padding-left:0px; + display: block; + width: 100%; + text-align: center !important; + } + .loan-header { + font-size:30px; + font-weight:400; + text-align: center; + } +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.spec.ts new file mode 100644 index 0000000..b6c1b07 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.spec.ts @@ -0,0 +1,50 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { SelectLoanComponent } from './select-loan.component'; +import { configureTestSuite } from 'ng-bullet'; +import { MainCardComponent } from './cards/main-card/main-card.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationService } from '../shared/credit-application.service'; +import { MemberMeta } from '../member/models/member-meta'; +import { of } from 'rxjs'; + +describe('SelectLoanComponent', () => { + let component: SelectLoanComponent; + let service: CreditApplicationService; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([]), + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [ + SelectLoanComponent, + MainCardComponent, + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectLoanComponent); + service = TestBed.get(CreditApplicationService); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.ts new file mode 100644 index 0000000..1f891a0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/select-loan.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + + +@Component({ + selector: 'lw-select-loan', + templateUrl: './select-loan.component.html', + styleUrls: ['./select-loan.component.scss'] +}) +export class SelectLoanComponent { + + constructor() {} + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.html new file mode 100644 index 0000000..229f4f1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.html @@ -0,0 +1,21 @@ +
LANDING.VEHICLE
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.spec.ts new file mode 100644 index 0000000..e82725f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.spec.ts @@ -0,0 +1,37 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { SelectVehicleComponent } from './select-vehicle.component'; +import { SubCardComponent } from '../cards/sub-card/sub-card.component'; + +describe('SelectVehicleComponent', () => { + let component: SelectVehicleComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + SelectVehicleComponent, + SubCardComponent + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectVehicleComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.ts new file mode 100644 index 0000000..c088bfd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/select-vehicle.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-select-vehicle', + templateUrl: './select-vehicle.component.html', + styleUrls: ['../select-loan.component.scss'] +}) + +export class SelectVehicleComponent { + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.html new file mode 100644 index 0000000..16bc846 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.html @@ -0,0 +1,13 @@ +
LANDING.VEHICLE
+
+
+
+
+ +
+
+ +
+
+
+ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.spec.ts new file mode 100644 index 0000000..d2a7b92 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.spec.ts @@ -0,0 +1,37 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { SelectSubVehicleComponent } from './select-sub-vehicle.component'; +import { SubCardComponent } from '../../cards/sub-card/sub-card.component'; + +describe('SelectSubVehicleComponent', () => { + let component: SelectSubVehicleComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + SelectSubVehicleComponent, + SubCardComponent + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectSubVehicleComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.ts new file mode 100644 index 0000000..8e505d2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/select-loan/vehicle/sub-vehicle/select-sub-vehicle.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-select-sub-vehicle', + templateUrl: './select-sub-vehicle.component.html', + styleUrls: ['../../select-loan.component.scss'] +}) + +export class SelectSubVehicleComponent { + constructor() { } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-form-builder.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-form-builder.service.spec.ts new file mode 100644 index 0000000..b860750 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-form-builder.service.spec.ts @@ -0,0 +1,42 @@ +import { ReactiveFormsModule, FormGroup } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; +import { AddressFormBuilderService } from './address-form-builder.service'; +import { CommonGroups } from 'src/app/loan-wizard/field-groups/common-groups'; + + +describe('Address Form Builder Service', () => { + const formGroupName = 'someGroupName'; + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + it('should create addressVerifyInfoGroup and add it to the page form', () => { + const formGroup = new FormGroup({}); + AddressFormBuilderService.addAddressCompareGroupIfMissing(formGroup, formGroupName); + expect(formGroup.get(formGroupName)).toBeTruthy(); + CommonGroups.ADDRESS_VERIFY_STATUS_INFO.fields.forEach(control => { + expect(formGroup.get(formGroupName).get(control.field)).toBeTruthy(); + }); + }); + + it('should add validator function to the control', () => { + const formGroup = new FormGroup({}); + AddressFormBuilderService.addAddressCompareGroupIfMissing(formGroup, formGroupName); + formGroup.patchValue({addressVerifyStatus: false}); + formGroup.updateValueAndValidity(); + expect(formGroup.valid).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-form-builder.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-form-builder.service.ts new file mode 100644 index 0000000..d587fe3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-form-builder.service.ts @@ -0,0 +1,26 @@ +import { CommonGroups } from 'src/app/loan-wizard/field-groups/common-groups'; +import { FormGroup, FormControl } from '@angular/forms'; + +export class AddressFormBuilderService { + + public static addAddressCompareGroupIfMissing(group: FormGroup, groupName: string) { + if (group.get(groupName)) { + return; + } + const addressVerifyInfoGroup = this.createAddressVerifyInformationGroup(); + group.addControl(groupName, addressVerifyInfoGroup); + } + + public static createAddressVerifyInformationGroup(): FormGroup { + const addressVerifyInfoGroup = new FormGroup({}); + + CommonGroups.ADDRESS_VERIFY_STATUS_INFO.fields.forEach((eachField) => { + + const validators = eachField.validation.slice(); + addressVerifyInfoGroup.addControl(eachField.field, new FormControl('', validators)); + }, this); + + return addressVerifyInfoGroup; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation-format-converter.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation-format-converter.spec.ts new file mode 100644 index 0000000..1134747 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation-format-converter.spec.ts @@ -0,0 +1,93 @@ +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; +import { AddressValidationFormatConverter } from './address-validation-format-converter'; +import { AddressDiffInformation } from 'src/app/loan-wizard/field-groups/address-compare/address-compare-information'; +import { AddressInformation } from 'src/app/loan-wizard/field-groups/address/address-information'; +import { ApiAddress } from '../api-models/api-address'; + + +describe('Address Validation Format Service', () => { + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + it('should convert a UI Address to an ApiAddress', () => { + const apiAddress: ApiAddress = { + address1: '1980 OurHouseInDaMiddleOfOur St', + address2: 'Apt 39', + city: 'Madness', + state: 'UK', + postalCode: '54545' + }; + const uiAddress: AddressInformation = { + addressLine1: '1980 OurHouseInDaMiddleOfOur St', + addressLine2: 'Apt 39', + zipcode: '54545', + city: 'Madness', + state: 'UK' + }; + + expect(AddressValidationFormatConverter.makeApiAddress(uiAddress)).toEqual(apiAddress); + }); + + it('should convert ApiAddress back to UI Address', () => { + const apiAddress: ApiAddress = { + address1: '1980 OurHouseInDaMiddleOfOur St', + address2: 'Apt 39', + city: 'Madness', + state: 'UK', + postalCode: '54545' + }; + const uiAddress: AddressInformation = { + addressLine1: '1980 OurHouseInDaMiddleOfOur St', + addressLine2: 'Apt 39', + zipcode: '54545', + city: 'Madness', + state: 'UK' + }; + + expect(AddressValidationFormatConverter.makeUiAddress(apiAddress)).toEqual(uiAddress); + }); + + it('should detect differences (non case-sensitive)', () => { + const apiAddress: ApiAddress = { + address1: '1980 OURHOUSEINTHEMIDDLEOFOUR St', + address2: 'APT 39', + city: 'Madness', + state: 'UK', + postalCode: '54545', + postalCodeExt: '4444' + }; + const uiAddress: AddressInformation = { + addressLine1: '1980 OurHouseInDaMiddleOfOur Street', + addressLine2: 'Apt 39', + zipcode: '54544', + city: 'Madness', + state: 'UK' + }; + + const diff: AddressDiffInformation = { + address1: false, + address2: true, + city: true, + state: true, + postalCode: false, + allFieldsSame: false + }; + + expect(AddressValidationFormatConverter.getAddressValidationDiff(apiAddress, uiAddress)).toEqual(diff); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation-format-converter.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation-format-converter.ts new file mode 100644 index 0000000..63329ca --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation-format-converter.ts @@ -0,0 +1,67 @@ +import { ApiAddress } from '../api-models/api-address'; +import { AddressInformation } from 'src/app/loan-wizard/field-groups/address/address-information'; +import { AddressDiffInformation } from 'src/app/loan-wizard/field-groups/address-compare/address-compare-information'; + +export class AddressValidationFormatConverter { + + public static filterUiAddressInformation(data: any): AddressInformation { + if (!data) { + return; + } + return { + addressLine1: data.addressLine1, + addressLine2: data.addressLine2, + city: data.city, + state: data.state, + zipcode: data.zipcode, + zipcodeExt: data.zipcodeExt + }; + } + + public static getAddressValidationDiff(apiAddress: ApiAddress, userInput: AddressInformation): AddressDiffInformation { + const address1Diff = this.isFieldDifferent(apiAddress.address1, userInput.addressLine1); + const address2Diff = userInput.addressLine2 && apiAddress.address2 ? + this.isFieldDifferent(apiAddress.address2, userInput.addressLine2) : true; + const cityDiff = this.isFieldDifferent(apiAddress.city, userInput.city); + const stateDiff = this.isFieldDifferent(apiAddress.state, userInput.state); + const zipDiff = this.isFieldDifferent(apiAddress.postalCode, userInput.zipcode); + return { + address1: address1Diff, + address2: address2Diff, + city: cityDiff, + state: stateDiff, + postalCode: zipDiff, + allFieldsSame: address1Diff && address2Diff && cityDiff && stateDiff && zipDiff + }; + } + + public static isFieldDifferent(field1: string, field2: string): boolean { + if (!field1 || !field2) { + throw new Error('AddressValidationConverter got a null field'); + } + return field1.toLocaleUpperCase().trim() === field2.toLocaleUpperCase().trim(); + } + + public static makeApiAddress(addressModelValues: AddressInformation): ApiAddress { + return { + address1: addressModelValues.addressLine1, + address2: addressModelValues.addressLine2, + city: addressModelValues.city, + state: addressModelValues.state, + postalCode: addressModelValues.zipcode, + postalCodeExt: addressModelValues.zipcodeExt, + country: 'UnitedStates' + }; + } + + public static makeUiAddress(addressModelValues: ApiAddress): AddressInformation { + return { + addressLine1: addressModelValues.address1, + addressLine2: addressModelValues.address2, + city: addressModelValues.city, + state: addressModelValues.state, + zipcode: addressModelValues.postalCode, + zipcodeExt: addressModelValues.postalCodeExt + }; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation.service.ts new file mode 100644 index 0000000..0d6c7d6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/address-validation/address-validation.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { Observable } from 'rxjs'; +import { ApiAddress } from '../api-models/api-address'; + +@Injectable({ + providedIn: 'root' +}) +export class AddressValidationService { + + constructor(private httpClient: HttpClient) {} + + public buildAddressValidationURL(): string { + return environment.urlBase + '/AddressValidation/'; + } + + public getValidatedAddress(address: ApiAddress): Observable { + return this.httpClient.post(this.buildAddressValidationURL(), address); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-address.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-address.ts new file mode 100644 index 0000000..0ebcd85 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-address.ts @@ -0,0 +1,11 @@ + +export class ApiAddress { + public address1: string; + public address2?: string; + public city: string; + public state: string; // [AA, AE, AK, ...] + public postalCode: string; + public postalCodeExt?: string; + public country?: string; + public id?: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-employer.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-employer.ts new file mode 100644 index 0000000..d1756a6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-employer.ts @@ -0,0 +1,15 @@ +import { ApiIncome } from './api-income'; + +export class ApiEmployer { + public id?: string; + public name: string; + public currentEmployer: boolean; + public employmentType: string; // [ NoneSelected, Contract, FullTime, PartTime, SelfEmployed, Temporary ] + public title: string; + public income: ApiIncome; + public monthsEmployed?: number; + public startDate?: string; + public endDate?: string; + public workPhone?: string; + public language: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-enums.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-enums.ts new file mode 100644 index 0000000..0bd740e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-enums.ts @@ -0,0 +1,41 @@ + +export enum FrequencyTypeEnum { + 'Annually', 'SemiAnnually', 'Monthly', 'SemiMonthly', 'TwoWeeks', 'Weekly', 'Hourly' +} + +export enum ResidenceTypeEnum { + 'Own', 'Rent', 'Other' +} + +export enum OccupancyTypeEnum { + 'OwnerOccupied', 'SecondHome', 'Rental', 'VacationHome', 'Vacant' +} + +export enum PhoneTypeEnum { + 'Home', 'Mobile' +} + +export enum ValuationTypeEnum { + 'Invoice', 'Loan', 'MSRP', 'Retail', 'TradeIn', 'Wholesale', 'Manual' +} + +export enum MfgHomeTypeEnum { + 'Single', 'Double', 'Triple', 'Quad' +} + +// TODO: These enums are not used. Implement like vehicle or delete. +export enum PropertyTypeEnum { + 'SingleFamily', 'Condo', 'Townhouse', 'Family2_4', 'Other' +} + +export enum ConditionTypeEnum { + 'New', 'Used' +} + +export enum SellerTypeEnum { + 'Dealer', 'Private_Party' +} + +export enum UsageTypeEnum { + 'Personal', 'Business' +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-error.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-error.ts new file mode 100644 index 0000000..44f3fb8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-error.ts @@ -0,0 +1,12 @@ + +export class ApiError { + code: ApiErrorCode; + message: string; +} + +export enum ApiErrorCode { + No_Error, + Error_Updating, + Error_Submitting, + Error_Deleting +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-hmda-enums.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-hmda-enums.ts new file mode 100644 index 0000000..65e043e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-hmda-enums.ts @@ -0,0 +1,35 @@ +export const HMDA_RACES = { + americanIndianAlaskaNative: 'AmericanIndianAlaskaNative', + asian: 'Asian', + nativeHawaiian: 'NativeHawaiian', + blackOrAfricanAmerican: 'BlackOrAfricanAmerican', + white: 'White', + notProvided: 'NotProvided', + asianIndian: 'AsianIndian', + chinese: 'Chinese', + filipino: 'Filipino', + japanese: 'Japanese', + korean: 'Korean', + vietnamese: 'Vietnamese', + otherAsian: 'OtherAsian', + hawaiian: 'Hawaiian', + guamanianChamorro: 'GuamanianChamorro', + samoan: 'Samoan', + otherPacificIslander: 'OtherPacificIslander' +}; + +export const HMDA_ETHNICITIES = { + hispanicOrLatino: 'HispanicOrLatino', + notHispanicOrLatino: 'NotHispanicOrLatino', + notProvided: 'NotProvided', + mexican: 'Mexican', + puertoRican: 'PuertoRican', + cuban: 'Cuban', + otherHispanicLatino: 'OtherHispanicLatino' +}; + +export const HMDA_SEXES = { + female: 'Female', + male: 'Male', + notProvided: 'NotProvided' +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-hmda.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-hmda.ts new file mode 100644 index 0000000..0c39529 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-hmda.ts @@ -0,0 +1,11 @@ + +export class ApiHMDA { + public races: Array; + public ethnicities: Array; + public sexes: Array; + public americanIndianAlaskaNativeTribe?: string; + public otherAsianRace?: string; + public otherPacificIslanderRace?: string; + public otherHispanicLatinoEthnicity?: string; + public hmdaNoticeDisclosed?: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-income.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-income.ts new file mode 100644 index 0000000..d314acd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-income.ts @@ -0,0 +1,13 @@ +export class ApiIncome { + public incomeTypeCD: string; /* [Alimony, Bonuses, ChildSupport, RetirementDisability, Dividends, + Commissions, Other, Spouse, RentalProperty, Interest, SocialSecurity, + IndividualAccount, Salary] */ + public sourceName: string; + public alimonyStatus?: string; // [CourtOrdered, OralUnderstanding, Written] + public amountPerPeriod?: number; + public frequency: string; // [ Annually, SemiAnnually, Monthly, SemiMonthly, TwoWeeks, Weekly, Hourly] + public hoursPerWeek?: string; + public incomeAdjustmentPercentage?: number; + public adjustedIncome?: number; + public incomeStatus: string; // [IncomeStatus_Excluded, IncomeStatus_Included] +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-individual.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-individual.ts new file mode 100644 index 0000000..4d4b5ce --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-individual.ts @@ -0,0 +1,45 @@ +import { ApiResidenceAddress } from './api-residence-address'; +import { ApiIncome } from './api-income'; +import { ApiEmployer } from './api-employer'; +import { ApiTattleSpouse } from './api-tattle-spouse'; +import { ApiHMDA } from './api-hmda'; +import { ApiAddress } from './api-address'; +import { ApiSuffix } from './api-suffix'; + +export class ApiIndividual { + public firstName: string; + public middleInitial?: string; + public lastName: string; + public dob: string; + public suffix?: ApiSuffix; + public ssn: string; + public phone: string; + public phoneType: string; // [Mobile, Home] + public secondaryPhone?: string; + public secondaryPhoneType?: string; + public email: string; + public address: ApiResidenceAddress; + public previousAddresses?: Array; + public mailingAddress?: ApiAddress; + public employers: Array; + public otherIncomes: Array; + public races?: Array; + public americanIndianAlaskaNativeTribe?: string; + public otherAsianRace?: string; + public otherPacificIslanderRace?: string; + public ethnicities?: Array; + public otherHispanicLatinoEthnicity?: string; + public sexes?: Array; + public isMarried?: boolean; + public language: string; + public customerNumber?: string; + public id?: string; + public detailedNonApplicantSpouse?: ApiIndividual; + public nonApplicantSpouse?: ApiTattleSpouse; + public isMarriedToPrimaryBorrower?: boolean; + public hmdaValues?: ApiHMDA; + public isLandmarkEmployee?: boolean; + public hasOptedOutTCPA: boolean; + public primaryApplicantEmail?: string; + public coApplicantEmail?: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-lien-position.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-lien-position.ts new file mode 100644 index 0000000..15a2026 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-lien-position.ts @@ -0,0 +1,6 @@ +export class ApiLienPosition { + public hasFirstMortgage : boolean; + public hasSecondMortgage: boolean; + public payoffFirstMortgage: boolean; + public payoffSecondMortgage: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-loan-application.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-loan-application.ts new file mode 100644 index 0000000..ce203dc --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-loan-application.ts @@ -0,0 +1,30 @@ +import { ApiIndividual } from './api-individual'; +import { ApiRealEstate } from './api-real-estate'; +import { ApiVehicle } from './api-vehicle'; +import { ApiManufacturedHome } from './api-manufactured-home'; +import { LoanCode } from '../loan-type/loan-code'; +import { LoanPurpose } from '../loan-type/loan-purpose'; + +export class ApiLoanApplication { + public id: string; + public loanType: LoanCode; + public termLengthInMonths?: number; + public requestedAmount: number; + public primaryBorrower: ApiIndividual; + public coApplicant: ApiIndividual; + public purpose?: LoanPurpose; + public purposeOfLoanSignature?: string; + public date: string; + public disclosureDate?: Date; + public realEstate?: ApiRealEstate; + public vehicle?: ApiVehicle; + public manufacturedHome?: ApiManufacturedHome; + public modFeeAccount?: string; + public note?: string; + public primaryContactMethod: string; + public promoCode?: string; + public ipAddress?: string; + public loanTitle?: string; + public clientMessage?: string; + public agreementRequirement? : boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-loan-container.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-loan-container.ts new file mode 100644 index 0000000..dd5c095 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-loan-container.ts @@ -0,0 +1,21 @@ +import { ApiLoanApplication } from './api-loan-application'; +import { ApiPage } from './api-page'; +import { ApiStatus } from './api-status'; +import { ApiError } from './api-error'; +import { LoanCode } from '../loan-type/loan-code'; + +export class ApiLoanContainer { + public id: string; + public loanId: string; + public loanApplication: ApiLoanApplication; + public loanType: LoanCode; + public page: ApiPage; + public status: ApiStatus; + public error: ApiError; + public date: Date; + public firstName: string; + public lastName: string; + public branch: string; + public memberId?: string; + public individualId?: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-lot.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-lot.ts new file mode 100644 index 0000000..0f1b55d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-lot.ts @@ -0,0 +1,7 @@ +import { ApiAddress } from './api-address'; + +export class ApiPropertyLot extends ApiAddress { + public isRented: boolean; + public rentalAmt?: number; + public annualPropertyTax?: number; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-manufactured-home.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-manufactured-home.ts new file mode 100644 index 0000000..afd67c9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-manufactured-home.ts @@ -0,0 +1,12 @@ +import { MfgHomeTypeEnum, OccupancyTypeEnum } from './api-enums'; +import { ApiPropertyLot } from './api-lot'; + +export class ApiManufacturedHome { + public type: MfgHomeTypeEnum; + public use: OccupancyTypeEnum; + public id?: string; + public estimatedHomeValuation?: number; + public lot?: ApiPropertyLot; + public currentlyListedForSale: boolean; + public currentlyBeingRemodeledOrImproved: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-page.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-page.ts new file mode 100644 index 0000000..45f9771 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-page.ts @@ -0,0 +1,32 @@ + + +export class ApiPage { + public hasPersonal: boolean; + public hasAddress: boolean; + public hasEmployment: boolean; + public hasMemberInfo: boolean; + public hasSpouseInfo: boolean; + public hasTattleSpouseInfo: boolean; + public hasResidenceInfo: boolean; + public hasVehicleCollateral: boolean; + public hasHMDA: boolean; + public hasDisclosure: boolean; + public hasCollateralInfo: boolean; + public hasNote: boolean; + + constructor() { + this.hasPersonal = false; + this.hasAddress = false; + this.hasEmployment = false; + this.hasMemberInfo = false; + this.hasSpouseInfo = false; + this.hasTattleSpouseInfo = false; + this.hasResidenceInfo = false; + this.hasVehicleCollateral = false; + this.hasHMDA = false; + this.hasDisclosure = false; + this.hasCollateralInfo = false; + this.hasNote = false; + } +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-real-estate.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-real-estate.ts new file mode 100644 index 0000000..4dcb397 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-real-estate.ts @@ -0,0 +1,21 @@ +import { ApiAddress } from './api-address'; +import { ApiLienPosition } from './api-lien-position'; + +export class ApiRealEstate { + public type: string; + public use: string; + public currentlyListedForSale: boolean; + public currentlyBeingRemodeledOrImproved: boolean; + public lienPosition?: ApiLienPosition; + public initialEstimatedValue?: number; + public hasSpa?: boolean; + public hasPool?: boolean; + public hasBasement?: boolean; + public hasCentralAir?: boolean; + public hasGarage?: boolean; + public inFloodZone?: boolean; + public inEquity?: boolean; + public isPropertyHeldInTrust?: boolean; + public realEstateAddress?: ApiAddress; + public id?: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-residence-address.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-residence-address.ts new file mode 100644 index 0000000..b2ff674 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-residence-address.ts @@ -0,0 +1,7 @@ +import { ApiAddress } from './api-address'; + +export class ApiResidenceAddress extends ApiAddress { + public monthsResided: number; + public monthlyPayment?: number; + public residenceType: string; // [Own, Rent, Other] +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-status.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-status.ts new file mode 100644 index 0000000..c1432f4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-status.ts @@ -0,0 +1,10 @@ + + +export enum ApiStatus { + VENDOR_ERROR = 'Vendor_Error', + APPLICATION_ERROR = 'Application_Error', + IN_PROGRESS = 'In_Progress', + DELETED = 'Deleted', + SUBMITTED = 'Submitted', + DECISIONED = 'Decisioned' +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-suffix.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-suffix.ts new file mode 100644 index 0000000..cb41a7d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-suffix.ts @@ -0,0 +1,10 @@ + + +export enum ApiSuffix { + II, + III, + IV, + V, + Jr, + Sr +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-tattle-spouse.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-tattle-spouse.ts new file mode 100644 index 0000000..70c1972 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-tattle-spouse.ts @@ -0,0 +1,13 @@ +import { ApiAddress } from './api-address'; + +export class ApiTattleSpouse { + public firstName: string; + public middleInitial?: string; + public lastName: string; + public ssn?: string; + public email?: string; + public mailingAddress: ApiAddress; + public language: string; + public id?: string; + public hasOptedOutTCPA: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-vehicle.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-vehicle.ts new file mode 100644 index 0000000..2d43170 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-models/api-vehicle.ts @@ -0,0 +1,48 @@ +import { VehicleType } from '../vehicle-type/vehicle-type'; +import { ApiAddress } from './api-address'; + +export class ApiVehicle { + public valuationId?: string; + public valuationType: string; + public condition: string; + public seller?: string; + public type: VehicleType; + public usage?: string; + public hasClearTitle?: boolean; + public initialEstimatedValue: number; + public isForbusinessUse?: boolean; + public isOwnerOperator?: boolean; + public numberOfCylinders?: number; + public numberOfWheels?: number; + public year: number; + public mileage: number; + public make: string; + public model: string; + public color?: string; + public body?: string; + public style?: string; + public series?: string; + public trim?: string; + public vin?: string; + public id?: string; + public transferring?: string; + public licensePlateNumber?: string; + public dealerName?: string; + public dealerPhone?: string; + public stateTitled?: string; + public countyTitled?: string; + public titleBranded?: boolean; + public lien?: boolean; + public boatLength?: number; + public boatSameAddress?: string; + public boatAddress?: ApiAddress; + public trailerYear?: number; + public trailerMake?: string; + public trailerType?: string; // [Single, Tandem, Tri-Axle] + public motorYear?: number; + public motorMake?: string; + public motorModel?: string; + public motorType?: number; // [Inboard, Outboard, Inboard/Outboard] + public municipalityType?: string; + public municipalityWhereKept?: string; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/api-translator.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/api-translator.service.spec.ts new file mode 100644 index 0000000..19e72b7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/api-translator.service.spec.ts @@ -0,0 +1,164 @@ +import { CreditApplication } from '../credit-application'; +import { LoanTypeSet } from '../loan-type'; +import { ApiTranslatorService } from './api-translator.service'; +import { ApiLoanApplication } from '../api-models/api-loan-application'; +import { CreditApplicationValues } from '../credit-application-values'; +import { apiTranslatorPreTranslate } from '../../testing/shared-mocks/api-translator-pre-post'; +import { LoanPurpose } from '../loan-type/loan-purpose'; +import { ApiStatus } from '../api-models/api-status'; +import { ApiErrorCode } from '../api-models/api-error'; +import { PhoneTypeEnum } from '../api-models/api-enums'; +import { ApiLoanContainer } from '../api-models/api-loan-container'; +import { LoanCode } from '../loan-type/loan-code'; +import { ConfigurationService } from '../configuration/configuration.service'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { UiSettings } from '../configuration/ui-settings'; +import { DefaultSettings } from '../configuration/default-settings.mock'; +import { configureTestSuite } from 'ng-bullet'; + +describe('ApiTranslatorService', () => { + + let service: ApiTranslatorService; + let creditApplicationStub: CreditApplication; + let loanApplication: CreditApplication; + let configService: ConfigurationService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + ConfigurationService + ] + }).compileComponents(); + }); + beforeEach(() => { + service = TestBed.get(ApiTranslatorService); + configService = TestBed.get(ConfigurationService); + configService.saveConfiguration(defaultSettings); + creditApplicationStub = { + primaryContactMethod: '1234567890', + promoCode: 'abc-1235', + note: 'this is a test note', + loanId: '123334252345', + limitRequested: 5446, + loanType: LoanTypeSet[1], + hasBeenCompleted: false, + disclosureInformation: { + truthful: true, + contact: false, + date: new Date() + }, + hasCoApplicant: CreditApplicationValues.CHOICE_YES, + hasCoApplicantSpouse: CreditApplicationValues.CHOICE_NO, + fullInfoBorrowers: [{ + personalInformation: { + firstName: 'Frank', + lastName: 'Reynolds', + middleInitial: 'M', + dateOfBirth: '09-17-1991', + socialSecurityNumber: '999-88-7777', + primaryPhoneNumber: '(414) 439-0116', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + email: 'developer@git.com', + secondaryPhoneNumber: '(414) 867-5309', + secondaryPhoneNumberType: PhoneTypeEnum.Home + }, + addressInformation: { + addressLine1: '51 Gardner', + city: 'Alston', + state: 'MA', + zipcode: '54545' + }, + residenceInformation: { + residenceTimeMonths: '2', + residenceTimeYears: '3', + residenceStatus: '1', + residenceMonthlyPayment: 5445, + isMailingAddressSame: true + }, + }], + status: ApiStatus.IN_PROGRESS, + error: { + code: ApiErrorCode.No_Error, + message: 'No Errors' + }, + page: { + hasAddress: true, + hasEmployment: true, + hasPersonal: true, + hasMemberInfo: true, + hasSpouseInfo: true, + hasTattleSpouseInfo: true, + hasResidenceInfo: true, + hasVehicleCollateral: true, + hasHMDA: true, + hasDisclosure: true, + hasCollateralInfo: true, + hasNote: true + }, + }; + loanApplication = apiTranslatorPreTranslate; + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should translate a CreditApplication to a ApiLoanApplication', () => { + const apiApplication: ApiLoanApplication = service.translateToApiLoanApplication(creditApplicationStub); + expect(apiApplication).toBeTruthy(); + }); + + it('should translate a CreditApplication to a ApiLoanApplication with masked fields for reporting', () => { + const apiApplicationWithHiddenFields: ApiLoanApplication = service.translateToApiLoanApplicationHiddenValues(creditApplicationStub); + const apiApplication: ApiLoanApplication = service.translateToApiLoanApplication(creditApplicationStub); + expect(apiApplication).toBeTruthy(); + expect(apiApplicationWithHiddenFields).toBeTruthy(); + expect(apiApplication.primaryBorrower.firstName).toEqual(apiApplicationWithHiddenFields.primaryBorrower.firstName); + expect(apiApplication.primaryBorrower.lastName).not.toEqual(apiApplicationWithHiddenFields.primaryBorrower.lastName); + expect(apiApplication.primaryBorrower.ssn).not.toEqual(apiApplicationWithHiddenFields.primaryBorrower.ssn); + expect(apiApplication.primaryBorrower.address).not.toEqual(apiApplicationWithHiddenFields.primaryBorrower.address); + expect(apiApplication.primaryBorrower.phone).not.toEqual(apiApplicationWithHiddenFields.primaryBorrower.phone); + }); + + it('should translate a CreditApplication to an ApiLoanApplication (full)', () => { + const apiApplication = service.translateToApiLoanApplication(loanApplication); + expect(apiApplication).toBeTruthy(); + // GJF, I have to stop here for now. I will continue testing after merging. + }); + + it('should build loan container', () => { // TODO Figure out why personalInformation is populated from application-wizard spec + spyOn(configService, 'getAllConfiguration').and.returnValue(defaultSettings); + let loanContainer: ApiLoanContainer = new ApiLoanContainer(); + loanContainer = service.translateToApiLoanContainer(creditApplicationStub); + expect(loanContainer.id).toEqual('123334252345'); + expect(loanContainer.loanType).toEqual(LoanCode.CC_Rewards); + expect(loanContainer.status).toEqual(ApiStatus.IN_PROGRESS); + expect(loanContainer.firstName).toEqual('Frank'); + expect(loanContainer.lastName).toEqual('Reynolds'); + expect(loanContainer.branch).toEqual('Not Found'); + }); + + it('should build note data on summary page', () => { + spyOn(configService, 'getAllConfiguration').and.returnValue(defaultSettings); + let loanContainer: ApiLoanContainer = new ApiLoanContainer(); + loanContainer = service.translateToApiLoanContainer(creditApplicationStub); + expect(loanContainer.loanApplication.promoCode).toEqual('abc-1235'); + expect(loanContainer.loanApplication.note).toEqual('this is a test note'); + expect(loanContainer.loanApplication.primaryContactMethod).toEqual('1234567890'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/api-translator.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/api-translator.service.ts new file mode 100644 index 0000000..c336cb3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/api-translator.service.ts @@ -0,0 +1,80 @@ +import { Injectable } from '@angular/core'; +import { ApiLoanApplication } from '../api-models/api-loan-application'; +import { CreditApplication } from '../credit-application'; +import { EquityTranslator } from './loan-type-translators/equity-translator'; +import { VehicleTranslator } from './loan-type-translators/vehicle-translator'; +import { HeaderTranslator } from './header-translator'; +import { CreditCardTranslator } from './loan-type-translators/credit-card-translator'; +import { IndividualTranslator } from './individual-translator'; +import { SignatureLoanTranslator } from './loan-type-translators/signature-loan-translator'; +import { LoanCategory } from '../loan-type/loan-category'; +import { ApiLoanContainer } from '../api-models/api-loan-container'; +import { BranchOptions } from '../options/branch-options'; +import { ConfigurationService } from '../configuration/configuration.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ApiTranslatorService { + constructor( + private configurationService: ConfigurationService + ) { } + + public translateToApiLoanContainer(creditApplication: CreditApplication): ApiLoanContainer { + const apiLoanApplication: ApiLoanApplication = this.translateToApiLoanApplication(creditApplication); + const uiSettings = this.configurationService.getAllConfiguration(); + const apiLoanContainer = new ApiLoanContainer(); + apiLoanContainer.loanApplication = apiLoanApplication; + apiLoanContainer.id = creditApplication.loanId; + apiLoanContainer.loanType = apiLoanApplication.loanType; + apiLoanContainer.status = creditApplication.status; + apiLoanContainer.page = creditApplication.page; + apiLoanContainer.error = creditApplication.error; + apiLoanContainer.firstName = apiLoanApplication.primaryBorrower.firstName; + apiLoanContainer.lastName = apiLoanApplication.primaryBorrower.lastName; + apiLoanContainer.branch = BranchOptions.getBranchNameByZip(apiLoanApplication.primaryBorrower.address.postalCode, uiSettings); + return apiLoanContainer; + } + + public translateToApiLoanApplication(creditApplication: CreditApplication): ApiLoanApplication { + + const apiLoanApplication = new ApiLoanApplication(); + const headerTranslator = this.createHeaderTranslatorForApplication(creditApplication); + Object.assign(apiLoanApplication, headerTranslator.createApiLoanHeader(creditApplication)); + Object.assign(apiLoanApplication, headerTranslator.createCollateralFields(creditApplication)); + Object.assign(apiLoanApplication, headerTranslator.createVehicleFields(creditApplication)); + + const individualTranslator = new IndividualTranslator(); + Object.assign(apiLoanApplication, individualTranslator.createPrimaryBorrower(creditApplication)); + Object.assign(apiLoanApplication, individualTranslator.createSecondaryBorrower(creditApplication)); + + return apiLoanApplication; + } + + public translateToApiLoanApplicationHiddenValues(creditApplication: CreditApplication): ApiLoanApplication { + + const apiLoanApplication = new ApiLoanApplication(); + const headerTranslator = this.createHeaderTranslatorForApplication(creditApplication); + Object.assign(apiLoanApplication, headerTranslator.createApiLoanHeader(creditApplication)); + Object.assign(apiLoanApplication, headerTranslator.createCollateralFields(creditApplication)); + Object.assign(apiLoanApplication, headerTranslator.createVehicleFields(creditApplication)); + + const individualTranslator = new IndividualTranslator(); + Object.assign(apiLoanApplication, individualTranslator.createPrimaryBorrowerWithMaskedData(creditApplication)); + Object.assign(apiLoanApplication, individualTranslator.createSecondaryBorrowerWithMaskedData(creditApplication)); + return apiLoanApplication; + } + + public createHeaderTranslatorForApplication(creditApplication: CreditApplication): HeaderTranslator { + if (creditApplication.loanType.category === LoanCategory.Equity) { + return new EquityTranslator(); + } else if (creditApplication.loanType.category === LoanCategory.Vehicle) { + return new VehicleTranslator(); + } else if (creditApplication.loanType.category === LoanCategory.Signature) { + return new SignatureLoanTranslator(); + } else { + return new CreditCardTranslator(); + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/address-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/address-translator.ts new file mode 100644 index 0000000..a6c8f96 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/address-translator.ts @@ -0,0 +1,19 @@ +import { ApiAddress } from '../../api-models/api-address'; +import { CreditApplicationValues } from '../../credit-application-values'; + +export class AddressTranslator { + public static readonly DEFAULT_COUNTRY = 'UnitedStates'; + + public static generate(addressData): ApiAddress { + return { + address1: addressData.addressLine1, + address2: addressData.addressLine2 || null, // Make test pass, GJF. + city: addressData.city, + state: addressData.state !== CreditApplicationValues.UNDEFINED ? addressData.state : '', + postalCode: addressData.zipcode, + postalCodeExt: addressData.zipcodeExt || null, + country: this.DEFAULT_COUNTRY + }; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/elements-translator.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/elements-translator.spec.ts new file mode 100644 index 0000000..935764a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/elements-translator.spec.ts @@ -0,0 +1,258 @@ +import { CreditApplication } from '../../credit-application'; +import { LoanTypeSet } from '../../loan-type'; +import { CreditApplicationValues } from '../../credit-application-values'; +import { EmployerTranslator } from './employer-translator'; +import { IncomeOtherTranslator } from './income-other-translator'; +import { IncomeAlimonyTranslator } from './income-alimony-translator'; +import { HmdaTranslator } from './hmda-translator'; +import { HMDA_ETHNICITIES, HMDA_RACES, HMDA_SEXES } from '../../api-models/api-hmda-enums'; +import { IncomeEmployerTranslator } from './income-employer-translator'; +import { ApiStatus } from '../../api-models/api-status'; +import { ApiError, ApiErrorCode } from '../../api-models/api-error'; +import { PhoneTypeEnum } from '../../api-models/api-enums'; + + +describe('Elements Translators', () => { + + let creditApplicationStub: CreditApplication; + + beforeEach(() => { + creditApplicationStub = new CreditApplication(LoanTypeSet[0]); + creditApplicationStub = { + primaryContactMethod: '1234567890', + limitRequested: 5446, + loanType: LoanTypeSet[1], + hasBeenCompleted: false, + disclosureInformation: { + contact: true, + truthful: true + }, + hasCoApplicant: CreditApplicationValues.CHOICE_YES, + hasCoApplicantSpouse: CreditApplicationValues.CHOICE_NO, + fullInfoBorrowers: [{ + personalInformation: { + firstName: 'Frank', + lastName: 'Reynolds', + middleInitial: 'M', + dateOfBirth: '09-17-1991', + socialSecurityNumber: '999-88-7777', + primaryPhoneNumber: '(414) 439-0116', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + email: 'developer@git.com', + secondaryPhoneNumber: '(414) 867-5309', + secondaryPhoneNumberType: PhoneTypeEnum.Home + }, + addressInformation: { + addressLine1: '51 Gardner', + city: 'Alston', + state: 'MA', + zipcode: '54545' + }, + residenceInformation: { + residenceTimeMonths: '2', + residenceTimeYears: '3', + residenceStatus: '1', + residenceMonthlyPayment: 5445, + isMailingAddressSame: true + } + }], + status: ApiStatus.IN_PROGRESS, + error: { + code: ApiErrorCode.No_Error, + message: 'No Errors' + }, + page: { + hasAddress: true, + hasEmployment: true, + hasPersonal: true, + hasMemberInfo: true, + hasSpouseInfo: true, + hasTattleSpouseInfo: true, + hasResidenceInfo: true, + hasVehicleCollateral: true, + hasHMDA: true, + hasDisclosure: true, + hasCollateralInfo: true, + hasNote: true + }, + }; + }); + + it('should generate an ApiEmployer and ApiIncome from that employer', () => { + + const apiEmployer = EmployerTranslator.generate({ + employerName: 'NVISIA', + employmentType: 'FullTime', + jobTitle: 'Developer', + employmentLengthMonths: 11, + employmentLengthYears: 2, + schedule: 'Annually', + salary: 20000 + }, false); + expect(apiEmployer).toBeTruthy(); + expect(apiEmployer.monthsEmployed).toBe(35); + expect(apiEmployer.income.frequency).toBe('Annually'); + + const employerApiJson = { + 'language': 'English', + 'name': 'NVISIA', + 'currentEmployer': false, + 'employmentType': 'FullTime', + 'title': 'Developer', + 'workPhone': null, + 'income': { + 'incomeTypeCD': 'Salary', + 'sourceName': 'NVISIA', + 'amountPerPeriod': 20000, + 'frequency': 'Annually', + 'incomeStatus': 'IncomeStatus_Included', + 'hoursPerWeek': null + }, + 'monthsEmployed': 35, + }; + + expect(apiEmployer).toEqual(employerApiJson); + }); + + it('should generate ApiIncome from other income', () => { + const apiIncome = IncomeOtherTranslator.generate({ + incomeName: 'Lottery', + amount: 80000000 + }); + expect(apiIncome).toBeTruthy(); + expect(apiIncome.sourceName).toBe('Lottery'); + expect(apiIncome.incomeTypeCD).toBe('Other'); + + const incomeApiJson = { + 'incomeTypeCD': 'Other', + 'sourceName': 'Lottery', + 'frequency': 'Annually', + 'amountPerPeriod': 80000000, + 'incomeStatus': 'IncomeStatus_Included' + }; + + expect(apiIncome).toEqual(incomeApiJson); + }); + + it('should generate ApiIncome from alimony payments', () => { + const apiIncome = IncomeAlimonyTranslator.generate({ + childSupportAmount: 5446 + }); + expect(apiIncome).toBeTruthy(); + expect(apiIncome.amountPerPeriod).toEqual(5446); + expect(apiIncome.incomeTypeCD).toBe('Alimony'); + }); + + + it('should convert HMDA "race" responses to correct API enums', () => { + const hmdaGroup = HmdaTranslator.generateHMDAGroup({ + americanIndianAlaskaNative: true, + americanIndianAlaskaNativeText: 'Nanuk' + }, HMDA_RACES); + expect(hmdaGroup.length).toBe(1); + expect(hmdaGroup[0]).toBe('AmericanIndianAlaskaNative'); + }); + + it('should return default for no valid "race" responses', () => { + const hmdaGroup = HmdaTranslator.generateHMDAGroup({}, HMDA_RACES); + expect(hmdaGroup.length).toBe(1); + expect(hmdaGroup[0]).toBe('NotProvided'); + }); + + it('should convert HMDA "sex" responses to correct API enums', () => { + const hmdaGroup = HmdaTranslator.generateHMDAGroup({ + male: true + }, HMDA_SEXES); + expect(hmdaGroup.length).toBe(1); + expect(hmdaGroup[0]).toBe('Male'); + }); + + it('should return default for no valid "sex" responses', () => { + const hmdaGroup = HmdaTranslator.generateHMDAGroup({}, HMDA_SEXES); + expect(hmdaGroup.length).toBe(1); + expect(hmdaGroup[0]).toBe('NotProvided'); + }); + + it('should convert HMDA "ethnicity" responses to correct API enums', () => { + const hmdaGroup = HmdaTranslator.generateHMDAGroup({ + cuban: true, + puertoRican: true + }, HMDA_ETHNICITIES); + expect(hmdaGroup.length).toBe(2); + expect(hmdaGroup[0]).toBe('Cuban'); + }); + + it('should return default for no valid "ethnicity" responses', () => { + const hmdaGroup = HmdaTranslator.generateHMDAGroup({}, HMDA_ETHNICITIES); + expect(hmdaGroup.length).toBe(1); + expect(hmdaGroup[0]).toBe('NotProvided'); + }); + + it('should return all race, ethnicity, and sex information', () => { + spyOn(HmdaTranslator, 'generateHMDAGroup').and.callThrough(); + const formattedHMDAGroup = HmdaTranslator.generateEquityHMDAFields({ + sexes: { + female: true + }, + ethnicities: {}, + races: { + vietnamese: true, + otherPacificIslander: true, + otherPacificIslanderText: 'EasterIslander' + } + }); + expect(formattedHMDAGroup.hmdaNoticeDisclosed).toEqual(true); + expect(formattedHMDAGroup.sexes[0]).toBe('Female'); + expect(formattedHMDAGroup.ethnicities[0]).toBe('NotProvided'); + expect(formattedHMDAGroup.races[1]).toBe('OtherPacificIslander'); + expect(formattedHMDAGroup.races[0]).toBe('Vietnamese'); + expect(formattedHMDAGroup['otherPacificIslanderRace']).toBe('EasterIslander'); + }); + + + it('should return an hourly employment correctly', () => { + const inputEmployer = { + employerName: 'NVISIA', + employmentType: 'FullTime', + jobTitle: 'Developer', + employmentLengthMonths: 11, + employmentLengthYears: 2, + schedule: 'Annually', + salary: 20 + }; + const outputIncome = { + 'incomeTypeCD': 'Salary', + 'sourceName': 'NVISIA', + 'amountPerPeriod': 20, + 'frequency': 'Annually', + 'hoursPerWeek': null, + 'incomeStatus': 'IncomeStatus_Included' + }; + const employmentResult = IncomeEmployerTranslator.generate(inputEmployer); + expect( employmentResult ).toEqual( outputIncome ); + }); + + it('should return a salary employment correctly', () => { + const inputEmployer = { + employerName: 'NVISIA', + employmentType: 'FullTime', + jobTitle: 'Developer', + employmentLengthMonths: 11, + employmentLengthYears: 2, + schedule: 'Annually', + salary: 20000 + }; + const outputIncome = { + 'incomeTypeCD': 'Salary', + 'sourceName': 'NVISIA', + 'amountPerPeriod': 20000, + 'frequency': 'Annually', + 'hoursPerWeek': null, + 'incomeStatus': 'IncomeStatus_Included' + }; + const employmentResult = IncomeEmployerTranslator.generate(inputEmployer); + expect( employmentResult ).toEqual( outputIncome ); + }); + + // @TODO should return part time employment, with specified hours +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/employer-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/employer-translator.ts new file mode 100644 index 0000000..0237523 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/employer-translator.ts @@ -0,0 +1,26 @@ +import { EmploymentInformation } from '../../../loan-wizard/field-groups/employment/employment-information'; +import { ApiEmployer } from '../../api-models/api-employer'; +import { IncomeEmployerTranslator } from './income-employer-translator'; +import { MaskingService } from '../../masking/masking.service'; + +export class EmployerTranslator { + public static readonly DEFAULT_LANGUAGE = 'English'; + + public static generate(employerData: EmploymentInformation, isCurrent: boolean): ApiEmployer { + return { + name: employerData.employerName, + currentEmployer: isCurrent, + employmentType: employerData.employmentType, + title: employerData.jobTitle, + income: IncomeEmployerTranslator.generate(employerData), + monthsEmployed: this.calculateMonths(employerData.employmentLengthMonths, employerData.employmentLengthYears), + language: this.DEFAULT_LANGUAGE, + workPhone: employerData.workPhoneNumber ? MaskingService.unMaskNumber(employerData.workPhoneNumber) : null + }; + } + + public static calculateMonths(months, years) { + return parseInt(months, 10) + parseInt(years, 10) * 12; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/hmda-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/hmda-translator.ts new file mode 100644 index 0000000..c3e7e01 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/hmda-translator.ts @@ -0,0 +1,64 @@ +import { Individual } from '../../credit-application'; +import { ApiHMDA } from '../../api-models/api-hmda'; +import { HMDA_ETHNICITIES, HMDA_RACES, HMDA_SEXES } from '../../api-models/api-hmda-enums'; + +export class HmdaTranslator { + + public static generate(individual: Individual): {hmdaValues: ApiHMDA} { + return { + hmdaValues: this.generateEquityHMDAFields({ + ethnicities: individual.ethnicityInformation, + sexes: individual.genderInformation, + races: individual.raceInformation + } + ) + }; + } + + public static generateEquityHMDAFields(hmdaResponses): ApiHMDA { + const formattedHMDA = { + sexes: this.generateHMDAGroup(hmdaResponses.sexes, HMDA_SEXES), + races: this.generateHMDAGroup(hmdaResponses.races, HMDA_RACES), + ethnicities: this.generateHMDAGroup(hmdaResponses.ethnicities, HMDA_ETHNICITIES), + hmdaNoticeDisclosed: true + }; + if (hmdaResponses.ethnicities.otherHispanicLatinoText) { + Object.assign(formattedHMDA, {otherHispanicLatinoEthnicity: hmdaResponses.ethnicities.otherHispanicLatinoText}); + formattedHMDA.ethnicities.push(HMDA_ETHNICITIES.otherHispanicLatino); + } + if (hmdaResponses.races.americanIndianAlaskaNativeText) { + Object.assign(formattedHMDA, {americanIndianAlaskaNativeTribe: hmdaResponses.races.americanIndianAlaskaNativeText}); + formattedHMDA.races.push(HMDA_RACES.americanIndianAlaskaNative); + } + if (hmdaResponses.races.otherAsianText) { + Object.assign(formattedHMDA, {otherAsianRace: hmdaResponses.races.otherAsianText}); + formattedHMDA.races.push(HMDA_RACES.otherAsian); + } + if (hmdaResponses.races.otherPacificIslanderText) { + Object.assign(formattedHMDA, {otherPacificIslanderRace: hmdaResponses.races.otherPacificIslanderText}); + formattedHMDA.races.push(HMDA_RACES.otherPacificIslander); + } + return formattedHMDA; + } + + public static generateHMDAGroup(hmdaResponses, enumGroup): Array { + const filteredCollection = []; + let textOverride = false; + Object.keys(hmdaResponses).forEach(label => { + if (label.includes('Text') && hmdaResponses[label] && hmdaResponses[label].length > 0) { + textOverride = true; + } + if (hmdaResponses[label] === true) { + filteredCollection.push(enumGroup[label]); + } + }); + if (!filteredCollection.length && !textOverride) { + return JSON.stringify(enumGroup) === JSON.stringify(HMDA_RACES) ? + ['NotProvided'] + : + ['NotProvided']; + } + + return filteredCollection; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-alimony-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-alimony-translator.ts new file mode 100644 index 0000000..89a8f37 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-alimony-translator.ts @@ -0,0 +1,19 @@ +import { AlimonyInformation } from '../../../loan-wizard/field-groups/employment/alimony-information'; +import { ApiIncome } from '../../api-models/api-income'; +import { FrequencyTypeEnum } from '../../api-models/api-enums'; + +export class IncomeAlimonyTranslator { + + public static generate(incomeData: AlimonyInformation): ApiIncome { + return { + incomeTypeCD: 'Alimony', + // @TODO: We do not have a protocol for naming Alimony + sourceName: 'Child Support', + // @TODO: We do not have a protocol for determining 'other income frequency' + frequency: FrequencyTypeEnum[0], + amountPerPeriod: incomeData.childSupportAmount, + incomeStatus: 'IncomeStatus_Included' + }; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-employer-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-employer-translator.ts new file mode 100644 index 0000000..de61a73 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-employer-translator.ts @@ -0,0 +1,27 @@ +import { ApiIncome } from '../../api-models/api-income'; + +export class IncomeEmployerTranslator { + + public static generate(incomeData): ApiIncome { + return { + sourceName: incomeData.employerName, + frequency: incomeData.schedule, + amountPerPeriod: incomeData.salary, + incomeTypeCD: 'Salary', + incomeStatus: 'IncomeStatus_Included', + hoursPerWeek: this.determineHoursPerWeek(incomeData) + }; + } + + // EmploymentType: FullTime, PartTime, or Contract + // HoursPerWeek = ? + public static determineHoursPerWeek(incomeData: any) { + if (incomeData.schedule === 'Hourly') { + if (incomeData.employmentType === 'FullTime') { + return '40'; + } + return incomeData.weeklyHours; + } + return null; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-other-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-other-translator.ts new file mode 100644 index 0000000..71b0fde --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/income-other-translator.ts @@ -0,0 +1,17 @@ +import { ApiIncome } from '../../api-models/api-income'; +import { FrequencyTypeEnum } from '../../api-models/api-enums'; +import { IncomeInformation } from '../../../loan-wizard/field-groups/employment/income-information'; + +export class IncomeOtherTranslator { + + public static generate(incomeData: IncomeInformation): ApiIncome { + return { + incomeTypeCD: 'Other', + sourceName: incomeData.incomeName, + // @TODO: We do not have a protocol for determining 'other income frequency' + frequency: FrequencyTypeEnum[0], + amountPerPeriod: incomeData.amount, + incomeStatus: 'IncomeStatus_Included' + }; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/personal-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/personal-translator.ts new file mode 100644 index 0000000..d7d8c45 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/personal-translator.ts @@ -0,0 +1,36 @@ +import { Individual } from '../../credit-application'; +import { CreditApplicationValues } from '../../credit-application-values'; +import { MaskingService } from '../../masking/masking.service'; +import { PhoneTypeEnum } from '../../api-models/api-enums'; + +export class PersonalTranslator { + public static readonly DEFAULT_COUNTRY = 'UnitedStates'; + + public static generate(individual: Individual) { + if (individual.personalInformation) { + const personalData = individual.personalInformation; + let married = personalData.maritalStatus === CreditApplicationValues.CHOICE_YES; + if (!married) { + married = personalData.maritalStatus === CreditApplicationValues.CHOICE_NO ? false : null; + } + return { + firstName: personalData.firstName, + middleInitial: personalData.middleInitial ? personalData.middleInitial : null, + lastName: personalData.lastName, + suffix: personalData.suffix ? personalData.suffix : null, + dob: personalData.dateOfBirth.toString(), + ssn: MaskingService.unMaskNumber(personalData.socialSecurityNumber), + phone: MaskingService.unMaskNumber(personalData.primaryPhoneNumber), + phoneType: PhoneTypeEnum[personalData.primaryPhoneNumberType], + secondaryPhone: personalData.secondaryPhoneNumber ? MaskingService.unMaskNumber(personalData.secondaryPhoneNumber) : null, + secondaryPhoneType: personalData.secondaryPhoneNumber ? PhoneTypeEnum[personalData.secondaryPhoneNumberType] : null, + email: personalData.email, + isMarried: married, + primaryApplicantEmail: personalData.primaryApplicantEmail, + coApplicantEmail: personalData.coApplicantEmail + }; + } + return null; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/residence-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/residence-translator.ts new file mode 100644 index 0000000..dd3ffc2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/residence-translator.ts @@ -0,0 +1,18 @@ +import { ResidenceTypeEnum } from '../../api-models/api-enums'; + +export class ResidenceTranslator { + + public static generate(residenceData) { + return { + residenceType: ResidenceTypeEnum[residenceData.residenceStatus], + monthsResided: this.calculateMonths(residenceData.residenceTimeMonths, residenceData.residenceTimeYears), + monthlyPayment: (residenceData.residenceMonthlyPayment || residenceData.residenceMonthlyPayment === 0) ? + residenceData.residenceMonthlyPayment : null + }; + } + + public static calculateMonths(months, years): number { + return parseInt(months, 10) + parseInt(years, 10) * 12; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/tattle-spouse-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/tattle-spouse-translator.ts new file mode 100644 index 0000000..7a18817 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/elements/tattle-spouse-translator.ts @@ -0,0 +1,25 @@ +import { ApiTattleSpouse } from '../../api-models/api-tattle-spouse'; +import { AddressTranslator } from './address-translator'; +import { Individual } from '../../credit-application'; +import { CreditApplicationValues } from '../../credit-application-values'; + +export class TattleSpouseTranslator { + public static readonly DEFAULT_LANGUAGE = 'English'; + + public static generate(individual: Individual): ApiTattleSpouse { + const tattleSpouse = individual.tattleSpouse; + const address = tattleSpouse.hasSameAddressAsSpouse === CreditApplicationValues.CHOICE_YES ? + individual.addressInformation + : + tattleSpouse.addressInformation; + return { + firstName: tattleSpouse.personalInformation.firstName, + middleInitial: tattleSpouse.personalInformation.middleInitial, + lastName: tattleSpouse.personalInformation.lastName, + mailingAddress: AddressTranslator.generate(address), + language: this.DEFAULT_LANGUAGE, + hasOptedOutTCPA: null + }; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/header-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/header-translator.ts new file mode 100644 index 0000000..c88bacb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/header-translator.ts @@ -0,0 +1,11 @@ +import { CreditApplication } from '../credit-application'; + +export interface HeaderTranslator { + + createApiLoanHeader(creditApplication: CreditApplication): any; + + createCollateralFields(creditApplication: CreditApplication): any; + + createVehicleFields(creditApplication: CreditApplication): any; + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/individual-translator.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/individual-translator.spec.ts new file mode 100644 index 0000000..87c64f7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/individual-translator.spec.ts @@ -0,0 +1,462 @@ +import { CreditApplication, Individual, SpouseType, TattleIndividual } from '../credit-application'; +import { LoanTypeSet } from '../loan-type'; +import { CreditApplicationValues } from '../credit-application-values'; +import { IndividualTranslator } from './individual-translator'; +import { ApiIndividual } from '../api-models/api-individual'; +import { ApiAddress } from '../api-models/api-address'; +import { ApiResidenceAddress } from '../api-models/api-residence-address'; +import { ApiStatus } from '../api-models/api-status'; +import { ApiErrorCode } from '../api-models/api-error'; +import { PhoneTypeEnum } from '../api-models/api-enums'; + +describe('Individual Translator', () => { + + let service: IndividualTranslator; + let creditApplicationStub: CreditApplication; + let spouse: Individual; + beforeEach(() => { + service = new IndividualTranslator(); + + creditApplicationStub = { + primaryContactMethod: '1234567890', + limitRequested: 5446, + loanType: LoanTypeSet[1], + hasBeenCompleted: false, + disclosureInformation: { + contact: true, + truthful: true, + date: new Date() + }, + hasCoApplicant: CreditApplicationValues.CHOICE_YES, + hasCoApplicantSpouse: CreditApplicationValues.CHOICE_NO, + fullInfoBorrowers: [{ + personalInformation: { + firstName: 'Frank', + lastName: 'Reynolds', + middleInitial: 'M', + dateOfBirth: '09-17-1991', + socialSecurityNumber: '999-88-7777', + primaryPhoneNumber: '(414) 439-0116', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + email: 'developer@git.com', + secondaryPhoneNumber: '(414) 867-5309', + secondaryPhoneNumberType: PhoneTypeEnum.Home + }, + addressInformation: { + addressLine1: '51 Gardner', + city: 'Alston', + state: 'MA', + zipcode: '54545' + }, + residenceInformation: { + residenceTimeMonths: '2', + residenceTimeYears: '3', + residenceStatus: '1', + residenceMonthlyPayment: 5445, + isMailingAddressSame: true + }, + employmentInformation: [{ + employerName: 'Johnson', + employmentType: 'Part Time', + jobTitle: 'Engineer', + salary: 60000, + schedule: 'Annually', + employmentLengthYears: 3, + employmentLengthMonths: 6, + weeklyHours: 40, + workPhoneNumber: '(414) 888-9999' + }], + otherIncomeInformation: [{ + amount: 100, + incomeName: 'Mowing', + frequency: '' + }], + alimonyInformation: { + childSupportAmount: 256 + } + }, + { + personalInformation: { + firstName: 'Jane', + lastName: 'Reynolds', + middleInitial: 'M', + dateOfBirth: '09-19-1991', + socialSecurityNumber: '999-88-7777', + primaryPhoneNumber: '(414) 439-0116', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + email: 'developer@git.com', + secondaryPhoneNumber: '(414) 867-5309', + secondaryPhoneNumberType: PhoneTypeEnum.Home + }, + addressInformation: { + addressLine1: '51 Gardner', + city: 'Alston', + state: 'MA', + zipcode: '54547' + }, + residenceInformation: { + residenceTimeMonths: '1', + residenceTimeYears: '3', + residenceStatus: '1', + residenceMonthlyPayment: 5445, + isMailingAddressSame: true + }, + employmentInformation: [{ + employerName: 'Ville', + employmentType: 'Full Time', + jobTitle: 'Engineer', + salary: 50000, + schedule: 'Annually', + employmentLengthYears: 2, + employmentLengthMonths: 2, + weeklyHours: 40, + workPhoneNumber: '(414) 777-9999' + }], + otherIncomeInformation: [{ + amount: 200, + incomeName: 'Shoveling', + frequency: '' + }], + alimonyInformation: { + childSupportAmount: 512 + } + }], + status: ApiStatus.IN_PROGRESS, + error: { + code: ApiErrorCode.No_Error, + message: 'No Errors' + }, + page: { + hasAddress: true, + hasEmployment: true, + hasPersonal: true, + hasMemberInfo: true, + hasSpouseInfo: true, + hasTattleSpouseInfo: true, + hasResidenceInfo: true, + hasVehicleCollateral: true, + hasHMDA: true, + hasDisclosure: true, + hasCollateralInfo: true, + hasNote: true + }, + }; + spouse = { + personalInformation: { + firstName: 'Spousy', + lastName: 'McSpouseface', + dateOfBirth: '4-29-1992', + socialSecurityNumber: '444-44-4444', + primaryPhoneNumber: '262-796-4500', + primaryPhoneNumberType: PhoneTypeEnum.Home, + email: 'GreatSpouse@same-address.org' + }, + addressInformation: { + addressLine1: '', + city: '', + state: '', + zipcode: '' + }, + residenceInformation: { + residenceStatus: '', + residenceMonthlyPayment: null, + residenceTimeMonths: '', + residenceTimeYears: '' + }, + hasSameAddressAsSpouse: CreditApplicationValues.CHOICE_YES + }; + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create a primary borrower', () => { + const individual = service.createPrimaryBorrower(creditApplicationStub); + expect(individual).toBeTruthy(); + expect(individual.primaryBorrower).toBeTruthy(); + const primaryBorrower = individual.primaryBorrower; + + expect(primaryBorrower).toBeTruthy(); + expect(primaryBorrower.isMarried).toBeFalsy(); + expect(primaryBorrower.dob).toBe('09-17-1991'); + expect(primaryBorrower.firstName).toBe('Frank'); + }); + + it('should create a secondary borrower', () => { + const individual = service.createSecondaryBorrower(creditApplicationStub); + expect(individual).toBeTruthy(); + expect(individual.coApplicant).toBeTruthy(); + const secondaryBorrower = individual.coApplicant; + + expect(secondaryBorrower).toBeTruthy(); + expect(secondaryBorrower.isMarried).toBeFalsy(); + expect(secondaryBorrower.dob).toBe('09-19-1991'); + expect(secondaryBorrower.firstName).toBe('Jane'); + }); + + it('should NOT create a secondary borrower', () => { + creditApplicationStub.hasCoApplicant = null; + const individual = service.createSecondaryBorrower(creditApplicationStub); + expect(individual).toEqual(null); + }); + + it('should build a secondary borrower with co-applicant spouse', () => { + creditApplicationStub.hasCoApplicantSpouse = CreditApplicationValues.CHOICE_YES; + const individual = service.buildSecondaryBorrower(creditApplicationStub); + expect(individual).toBeTruthy(); + expect(individual.coApplicant).toBeTruthy(); + expect(individual.coApplicant.isMarriedToPrimaryBorrower).toBe(true); + }); + + it('should build a secondary borrower with NO co-applicant spouse', () => { + const individual = service.buildSecondaryBorrower(creditApplicationStub); + expect(individual).toBeTruthy(); + expect(individual.coApplicant).toBeTruthy(); + expect(individual.coApplicant.isMarriedToPrimaryBorrower).toBe(false); + }); + + it('should determine if individual hasOptedOutTCPAValue', () => { + const hasOptedOutTCPAValue = service.hasOptedOutTCPAValue(creditApplicationStub.disclosureInformation); + expect(hasOptedOutTCPAValue).toBe(false); + }); + + it('should generate an ApiAddress', () => { + const addressApiJson = { + 'country': 'UnitedStates', + 'address1': '51 Gardner', + 'address2': null, + 'city': 'Alston', + 'state': 'MA', + 'postalCode': '54545', + 'postalCodeExt': null, + 'monthsResided': 38, + 'residenceType': 'Rent', + 'monthlyPayment': 5445 + }; + const individual = service.createPrimaryBorrower(creditApplicationStub); + const apiAddress = individual.primaryBorrower.address; + expect(apiAddress).toBeTruthy(); + expect(apiAddress.residenceType).toBe('Rent'); + expect(apiAddress.monthsResided).toBe(38); + expect(apiAddress.postalCode).toBe('54545'); + expect(apiAddress).toEqual(addressApiJson); + }); + + it('should generate an ApiAddress for secondary borrow', () => { + const addressApiJson: ApiResidenceAddress = { + 'country': 'UnitedStates', + 'address1': '51 Gardner', + 'address2': null, + 'city': 'Alston', + 'state': 'MA', + 'postalCode': '54547', + 'postalCodeExt': null, + 'monthsResided': 37, + 'residenceType': 'Rent', + 'monthlyPayment': 5445 + }; + const individual = service.createSecondaryBorrower(creditApplicationStub); + const apiAddress = individual.coApplicant.address; + expect(apiAddress).toBeTruthy(); + expect(apiAddress.residenceType).toBe('Rent'); + expect(apiAddress.monthsResided).toBe(37); + expect(apiAddress.postalCode).toBe('54547'); + expect(apiAddress).toEqual(addressApiJson); + }); + + it('should generate otherIncomes for primaryBorrower', () => { + const individual = service.createPrimaryBorrower(creditApplicationStub); + expect(individual).toBeTruthy(); + expect(individual.primaryBorrower).toBeTruthy(); + const primaryBorrower = individual.primaryBorrower; + + expect(primaryBorrower).toBeTruthy(); + expect(primaryBorrower.otherIncomes[0].sourceName).toBe('Mowing'); + expect(primaryBorrower.otherIncomes[0].amountPerPeriod).toBe(100); + expect(primaryBorrower.otherIncomes[1].sourceName).toBe('Child Support'); + expect(primaryBorrower.otherIncomes[1].amountPerPeriod).toBe(256); + }); + + it('should generate otherIncomes for secondaryBorrower', () => { + const individual = service.createSecondaryBorrower(creditApplicationStub); + expect(individual).toBeTruthy(); + expect(individual.coApplicant).toBeTruthy(); + const secondaryBorrower = individual.coApplicant; + + expect(secondaryBorrower).toBeTruthy(); + expect(secondaryBorrower.otherIncomes[0].sourceName).toBe('Shoveling'); + expect(secondaryBorrower.otherIncomes[0].amountPerPeriod).toBe(200); + expect(secondaryBorrower.otherIncomes[1].sourceName).toBe('Child Support'); + expect(secondaryBorrower.otherIncomes[1].amountPerPeriod).toBe(512); + + }); + + it('should generate employers for primaryBorrower', () => { + const individual = service.createPrimaryBorrower(creditApplicationStub); + expect(individual).toBeTruthy(); + expect(individual.primaryBorrower).toBeTruthy(); + const primaryBorrower = individual.primaryBorrower; + + expect(primaryBorrower).toBeTruthy(); + expect(primaryBorrower.employers[0].income.sourceName).toBe('Johnson'); + expect(primaryBorrower.employers[0].employmentType).toBe('Part Time'); + expect(primaryBorrower.employers[0].income.amountPerPeriod).toBe(60000); + }); + + it('should generate employers for secondaryBorrower', () => { + const individual = service.createSecondaryBorrower(creditApplicationStub); + expect(individual).toBeTruthy(); + expect(individual.coApplicant).toBeTruthy(); + const secondaryBorrower = individual.coApplicant; + + expect(secondaryBorrower).toBeTruthy(); + expect(secondaryBorrower.employers[0].income.sourceName).toBe('Ville'); + expect(secondaryBorrower.employers[0].employmentType).toBe('Full Time'); + expect(secondaryBorrower.employers[0].income.amountPerPeriod).toBe(50000); + }); + + it('should generate an ApiAddress for a mailing address', () => { + const mailingAddress: ApiAddress = { + country: 'UnitedStates', + address1: '12000 Alder Lake Rd', + address2: null, + city: 'Manitowish Waters', + state: 'WI', + postalCode: '54545', + postalCodeExt: null + }; + creditApplicationStub.fullInfoBorrowers[0].mailingAddressInformation = { + addressLine1: '12000 Alder Lake Rd', + city: 'Manitowish Waters', + state: 'WI', + zipcode: '54545' + }; + creditApplicationStub.fullInfoBorrowers[0].residenceInformation.isMailingAddressSame = false; + const individual: ApiIndividual = service.createPrimaryBorrower(creditApplicationStub).primaryBorrower; + expect(individual.mailingAddress).toEqual(mailingAddress); + }); + + it('should handle PRIMARY SPOUSE with the same address', () => { + creditApplicationStub.fullInfoBorrowers[0].fullInfoSpouse = spouse; + creditApplicationStub.fullInfoBorrowers[0].spouseType = SpouseType.FullInfoSpouse; + const primary = service.createPrimaryBorrower(creditApplicationStub); + expect(primary.primaryBorrower.detailedNonApplicantSpouse.address.address1).toEqual(primary.primaryBorrower.address.address1); + expect(primary.primaryBorrower.detailedNonApplicantSpouse.address.monthlyPayment).toBe(0); + }); + + it('should handle CO BORROWER SPOUSE with the same address', () => { + creditApplicationStub.hasCoApplicantSpouse = null; + creditApplicationStub.fullInfoBorrowers[1].fullInfoSpouse = spouse; + creditApplicationStub.fullInfoBorrowers[1].spouseType = SpouseType.FullInfoSpouse; + const secondary = service.createSecondaryBorrower(creditApplicationStub); + expect(secondary.coApplicant.detailedNonApplicantSpouse.address.address1).toEqual(secondary.coApplicant.address.address1); + expect(secondary.coApplicant.detailedNonApplicantSpouse.address.monthlyPayment).toBe(0); + }); + + it('should handle PRIMARY is married to CO BORROWER with the same address', () => { + creditApplicationStub.hasCoApplicantSpouse = CreditApplicationValues.CHOICE_YES; + creditApplicationStub.fullInfoBorrowers[1].addressInformation = { + addressLine1: '', + city: '', + state: '', + zipcode: '' + }; + creditApplicationStub.fullInfoBorrowers[1].residenceInformation = { + residenceStatus: '', + residenceTimeMonths: '', + residenceTimeYears: '' + }; + creditApplicationStub.fullInfoBorrowers[1].hasSameAddressAsSpouse = CreditApplicationValues.CHOICE_YES; + const primary = service.createPrimaryBorrower(creditApplicationStub); + const secondary = service.createSecondaryBorrower(creditApplicationStub); + creditApplicationStub.fullInfoBorrowers[1].hasSameAddressAsSpouse = CreditApplicationValues.CHOICE_YES; + expect(secondary.coApplicant.address.address1).toEqual(primary.primaryBorrower.address.address1); + expect(secondary.coApplicant.address.monthlyPayment).toBe(0); + }); + + it('should recognize a Landmark Credit Union employee', () => { + const individual = creditApplicationStub.fullInfoBorrowers[0]; + individual.employmentInformation = [{ + employerName: 'Landmark', + employmentType: 'Full time', + jobTitle: 'Project Manager', + schedule: 'Annually', + employmentLengthMonths: 3, + employmentLengthYears: 3, + salary: 120000 + }]; + const apiIndividualLandmarkEmployee: ApiIndividual = service.generateApiIndividual(individual, 'Credit Card'); + expect(apiIndividualLandmarkEmployee.isLandmarkEmployee).toBe(true); + individual.employmentInformation[0].employerName = 'Mandlark'; + const apiIndividualNonLandmarkEmployee: ApiIndividual = service.generateApiIndividual(individual, 'Credit Card'); + expect(apiIndividualNonLandmarkEmployee.isLandmarkEmployee).toBe(false); + }); + + it('should determine whether an individual has provided the minimal amount of data to save', () => { + const validIndividual = creditApplicationStub.fullInfoBorrowers[0]; + expect(service.individualHasMinimumInformationSaved(validIndividual)).toEqual(true); + const invalidIndividual1: Individual = { + personalInformation: { + firstName: '', + lastName: '', + dateOfBirth: '', + socialSecurityNumber: '', + primaryPhoneNumber: '', + primaryPhoneNumberType: PhoneTypeEnum.Home, + email: '' + }, + addressInformation: { + addressLine1: '', + city: '', + state: '', + zipcode: '' + }, + residenceInformation: { + residenceStatus: '', + residenceTimeMonths: '', + residenceTimeYears: '' + } + }; + expect(service.individualHasMinimumInformationSaved(invalidIndividual1)).toEqual(false); + const invalidIndividual2: TattleIndividual = { + personalInformation: { + firstName: '', + lastName: '' + }, + hasSameAddressAsSpouse: CreditApplicationValues.CHOICE_YES + }; + expect(service.individualHasMinimumInformationSaved(invalidIndividual2)).toEqual(false); + }); + + it('should require a co-applicant to have an address, regardless of spouse condition', () => { + const invalidIndividual2: TattleIndividual = { + personalInformation: { + firstName: 'JOhn', + lastName: 'Doe' + }, + addressInformation: { + addressLine1: '555 s 1st', addressLine2: '', city: '', state: 'WI', zipcode: '' + }, + hasSameAddressAsSpouse: CreditApplicationValues.CHOICE_YES + }; + expect(service.individualHasMinimumInformationSaved(invalidIndividual2)).toEqual(true); + invalidIndividual2.hasSameAddressAsSpouse = null; + expect(service.individualHasMinimumInformationSaved(invalidIndividual2)).toEqual(true); + invalidIndividual2.addressInformation = null; + expect(service.individualHasMinimumInformationSaved(invalidIndividual2)).toEqual(false); + invalidIndividual2.hasSameAddressAsSpouse = CreditApplicationValues.CHOICE_YES; + expect(service.individualHasMinimumInformationSaved(invalidIndividual2)).toEqual(true); + invalidIndividual2.hasSameAddressAsSpouse = CreditApplicationValues.CHOICE_NO; + expect(service.individualHasMinimumInformationSaved(invalidIndividual2)).toEqual(false); + invalidIndividual2.addressInformation = { + addressLine1: '555 s. 1st', addressLine2: '', city: '', state: 'WI', zipcode: '' + }; + expect(service.individualHasMinimumInformationSaved(invalidIndividual2)).toEqual(true); + }); + + it('should set all employers to current', () => { + expect(service.isCurrentEmployer(0)).toBe(true); + expect(service.isCurrentEmployer(3)).toBe(true); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/individual-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/individual-translator.ts new file mode 100644 index 0000000..997381b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/individual-translator.ts @@ -0,0 +1,294 @@ +import { CreditApplication, Individual, SpouseType, TattleIndividual } from '../credit-application'; +import { ApiIndividual } from '../api-models/api-individual'; +import { CreditApplicationValues } from '../credit-application-values'; +import { ApiEmployer } from '../api-models/api-employer'; +import { ApiResidenceAddress } from '../api-models/api-residence-address'; +import { AddressTranslator } from './elements/address-translator'; +import { IncomeAlimonyTranslator } from './elements/income-alimony-translator'; +import { IncomeOtherTranslator } from './elements/income-other-translator'; +import { PersonalTranslator } from './elements/personal-translator'; +import { TattleSpouseTranslator } from './elements/tattle-spouse-translator'; +import { ResidenceTranslator } from './elements/residence-translator'; +import { EmployerTranslator } from './elements/employer-translator'; +import { HmdaTranslator } from './elements/hmda-translator'; +import { DataTableService } from '../data-table.service'; +import { ApiTattleSpouse } from '../api-models/api-tattle-spouse'; +import { ApiAddress } from '../api-models/api-address'; +import { LoanCategory } from '../loan-type/loan-category'; +import { ApiHMDA } from '../api-models/api-hmda'; +import { ApiIncome } from '../api-models/api-income'; +import { DisclosureInformation } from 'src/app/loan-wizard/field-groups/disclosures/credit-card-disclosure'; + +export class IndividualTranslator { + public readonly DEFAULT_LANGUAGE = 'English'; + + public createPrimaryBorrower(creditApplication: CreditApplication): { primaryBorrower: ApiIndividual } { + const primary = { + primaryBorrower: this.generateApiIndividual(creditApplication.fullInfoBorrowers[0], creditApplication.loanType.category) + }; + + const hasOptedOutTCPAValue = this.hasOptedOutTCPAValue(creditApplication.disclosureInformation); + primary.primaryBorrower.hasOptedOutTCPA = hasOptedOutTCPAValue; + + if (primary.primaryBorrower.detailedNonApplicantSpouse) { + primary.primaryBorrower.detailedNonApplicantSpouse.isMarriedToPrimaryBorrower = true; + primary.primaryBorrower.detailedNonApplicantSpouse.hasOptedOutTCPA = hasOptedOutTCPAValue; + Object.assign(primary.primaryBorrower.detailedNonApplicantSpouse, + this.patchMaritalDependentFields( + creditApplication.fullInfoBorrowers[0], primary.primaryBorrower.detailedNonApplicantSpouse + ) + ); + } else if (primary.primaryBorrower.nonApplicantSpouse) { + primary.primaryBorrower.nonApplicantSpouse.hasOptedOutTCPA = hasOptedOutTCPAValue; + } + return primary; + } + + public createPrimaryBorrowerWithMaskedData(creditApplication: CreditApplication): { primaryBorrower: ApiIndividual } { + const primaryBorrower = this.createPrimaryBorrower(creditApplication); + return (primaryBorrower ? {primaryBorrower: this.createBorrowerWithMaskedData(primaryBorrower.primaryBorrower)} : primaryBorrower); + } + + public createSecondaryBorrowerWithMaskedData(creditApplication: CreditApplication): { coApplicant: ApiIndividual } { + const secondaryBorrower = this.createSecondaryBorrower(creditApplication); + return (secondaryBorrower ? {coApplicant: this.createBorrowerWithMaskedData(secondaryBorrower.coApplicant)} : secondaryBorrower); + } + + public createTattleSpouseWithMaskedData(tattleSpouse: ApiTattleSpouse): ApiTattleSpouse { + if (tattleSpouse) { + tattleSpouse.lastName = 'TATTLE_TEST'; + tattleSpouse.ssn = ''; + tattleSpouse.email = 'test@test.test'; + tattleSpouse.mailingAddress = null; + } + return tattleSpouse; + } + + public createBorrowerWithMaskedData(borrower: ApiIndividual): ApiIndividual { + if (borrower) { + borrower.lastName = 'TEST'; + borrower.dob = '01/01/1990'; + borrower.ssn = ''; + borrower.email = 'test@test.test'; + borrower.phone = '1112223333'; + borrower.address = null; + borrower.previousAddresses = null; + borrower.customerNumber = '0'; + borrower.employers = []; + if (borrower.detailedNonApplicantSpouse) { + borrower.detailedNonApplicantSpouse = this.createBorrowerWithMaskedData(borrower.detailedNonApplicantSpouse); + } + if (borrower.nonApplicantSpouse) { + borrower.nonApplicantSpouse = this.createTattleSpouseWithMaskedData(borrower.nonApplicantSpouse); + } + } + return borrower; + } + + public createSecondaryBorrower(creditApplication: CreditApplication): { coApplicant: ApiIndividual } { + if (creditApplication.hasCoApplicant && creditApplication.fullInfoBorrowers.length === 2 && + this.individualHasMinimumInformationSaved(creditApplication.fullInfoBorrowers[1])) { + return this.buildSecondaryBorrower(creditApplication); + } + return null; + } + + public buildSecondaryBorrower(creditApplication: CreditApplication): { coApplicant: ApiIndividual } { + const secondaryBorrower = { + coApplicant: this.generateApiIndividual(creditApplication.fullInfoBorrowers[1], creditApplication.loanType.category) + }; + + if (creditApplication.hasCoApplicantSpouse === CreditApplicationValues.CHOICE_YES) { + secondaryBorrower.coApplicant.isMarriedToPrimaryBorrower = true; + if (creditApplication.fullInfoBorrowers[1].hasSameAddressAsSpouse === CreditApplicationValues.CHOICE_YES) { + Object.assign(secondaryBorrower.coApplicant, + this.patchMaritalDependentFields( + creditApplication.fullInfoBorrowers[0], secondaryBorrower.coApplicant + ) + ); + } + } else { + secondaryBorrower.coApplicant.isMarriedToPrimaryBorrower = false; + if (secondaryBorrower.coApplicant.detailedNonApplicantSpouse && + creditApplication.fullInfoBorrowers[1].fullInfoSpouse.hasSameAddressAsSpouse === CreditApplicationValues.CHOICE_YES) { + Object.assign(secondaryBorrower.coApplicant.detailedNonApplicantSpouse, + this.patchMaritalDependentFields( + creditApplication.fullInfoBorrowers[1], secondaryBorrower.coApplicant.detailedNonApplicantSpouse + ) + ); + } + } + + const hasOptedOutTCPAValue = this.hasOptedOutTCPAValue(creditApplication.disclosureInformation); + secondaryBorrower.coApplicant.hasOptedOutTCPA = hasOptedOutTCPAValue; + + if (secondaryBorrower.coApplicant.detailedNonApplicantSpouse) { + secondaryBorrower.coApplicant.detailedNonApplicantSpouse.hasOptedOutTCPA = hasOptedOutTCPAValue; + } else if (secondaryBorrower.coApplicant.nonApplicantSpouse) { + secondaryBorrower.coApplicant.nonApplicantSpouse.hasOptedOutTCPA = hasOptedOutTCPAValue; + } + return secondaryBorrower; + } + + public patchMaritalDependentFields(individual: Individual, apiIndividual: ApiIndividual): ApiIndividual { + if (individual.fullInfoSpouse) { + if (individual.fullInfoSpouse.hasSameAddressAsSpouse === CreditApplicationValues.CHOICE_YES) { + Object.assign(apiIndividual, this.createApiAddress(individual)); + apiIndividual.address.monthlyPayment = 0; + } + } else { + Object.assign(apiIndividual, this.createApiAddress(individual)); + apiIndividual.address.monthlyPayment = 0; + } + return apiIndividual; + } + + public generateApiIndividual(individual: Individual, category: string): ApiIndividual { + const apiIndividual = new ApiIndividual(); + apiIndividual.language = this.DEFAULT_LANGUAGE; + + Object.assign(apiIndividual, PersonalTranslator.generate(individual)); + Object.assign(apiIndividual, this.createApiAddress(individual)); + Object.assign(apiIndividual, this.createApiMailingAddress(individual)); + Object.assign(apiIndividual, this.createPriorApiAddresses(individual)); + Object.assign(apiIndividual, this.createHMDAFields(individual, category)); + Object.assign(apiIndividual, this.createEmploymentFields(individual)); + Object.assign(apiIndividual, this.createOtherIncomeFields(individual)); + Object.assign(apiIndividual, this.createDetailedSpouseFields(individual, category)); + Object.assign(apiIndividual, this.createTattleSpouseFields(individual)); + Object.assign(apiIndividual, this.createIsLandmarkEmployeeField(individual)); + return apiIndividual; + } + + private createIsLandmarkEmployeeField(individual: Individual): { isLandmarkEmployee: boolean } { + if (individual.employmentInformation && individual.employmentInformation.length) { + const dataTableService = new DataTableService(); + return { + isLandmarkEmployee: dataTableService.isLandmarkEmployee(individual.employmentInformation[0].employerName) + }; + } + return null; + } + + public createHMDAFields(individual: Individual, category: string): { hmdaValues: ApiHMDA } { + if (individual.ethnicityInformation && individual.raceInformation && individual.genderInformation && category === LoanCategory.Equity) { + return HmdaTranslator.generate(individual); + } + return null; + } + + public createApiAddress(individual: Individual): { address: ApiResidenceAddress } { + if (individual.addressInformation && individual.residenceInformation) { + const apiAddress: ApiResidenceAddress = Object.assign({}, + AddressTranslator.generate(individual.addressInformation), + ResidenceTranslator.generate(individual.residenceInformation) + ); + return {address: apiAddress}; + } + return null; + } + + public createApiMailingAddress(individual: Individual): { mailingAddress: ApiAddress } { + if (individual.mailingAddressInformation) { + const apiAddress: ApiAddress = Object.assign({}, + AddressTranslator.generate(individual.mailingAddressInformation) + ); + return {mailingAddress: apiAddress}; + } + return null; + } + + private createPriorApiAddresses(individual: Individual): { previousAddresses: ApiResidenceAddress[] } { + if (individual.priorAddressInformation && + individual.priorResidenceInformation && + individual.priorAddressInformation.length === individual.priorResidenceInformation.length) { + + const priorAddresses: Array = []; + individual.priorAddressInformation.forEach((priorAddress, index) => { + priorAddresses.push( + Object.assign({}, + AddressTranslator.generate(priorAddress), + ResidenceTranslator.generate(individual.priorResidenceInformation[index]) + ) + ); + }); + return {previousAddresses: priorAddresses}; + } + return null; + } + + private createEmploymentFields(individual: Individual): { employers: ApiEmployer[] } { + if (individual.employmentInformation) { + const apiEmployerHistory: Array = []; + individual.employmentInformation.forEach((employer, i) => { + apiEmployerHistory.push(EmployerTranslator.generate(employer, this.isCurrentEmployer(i))); + }); + if (apiEmployerHistory.length) { + return { + employers: apiEmployerHistory + }; + } + } + return null; + } + + public isCurrentEmployer(employerNumber: number): boolean { + return true; + } + + private createOtherIncomeFields(individual: Individual): { otherIncomes: ApiIncome[] } { + const otherIncomes: Array = []; + if (individual.otherIncomeInformation) { + for (let i = 0; i < individual.otherIncomeInformation.length; i++) { + otherIncomes.push(IncomeOtherTranslator.generate(individual.otherIncomeInformation[i])); + } + } + if (individual.alimonyInformation) { + otherIncomes.push(IncomeAlimonyTranslator.generate(individual.alimonyInformation)); + } + + if (otherIncomes.length > 0) { + return {otherIncomes: otherIncomes}; + } + return null; + } + + private createDetailedSpouseFields(individual: Individual, category: string): { detailedNonApplicantSpouse: ApiIndividual } { + if (individual.spouseType === SpouseType.FullInfoSpouse && + individual.fullInfoSpouse && + this.individualHasMinimumInformationSaved(individual.fullInfoSpouse)) { + return {detailedNonApplicantSpouse: this.generateApiIndividual(individual.fullInfoSpouse, category)}; + } + return null; + } + + private createTattleSpouseFields(individual: Individual): { nonApplicantSpouse: ApiTattleSpouse } { + if (individual.spouseType === SpouseType.TattleSpouse && + individual.tattleSpouse && + this.individualHasMinimumInformationSaved(individual.tattleSpouse)) { + return {nonApplicantSpouse: TattleSpouseTranslator.generate(individual)}; + } + return null; + } + + public individualHasMinimumInformationSaved(individual: (Individual | TattleIndividual)): boolean { + return this.individualHasValidPersonalInformation(individual) && this.individualHasValidAddressInformation(individual); + } + + public individualHasValidAddressInformation(individual: (Individual | TattleIndividual)): boolean { + const addressInformation = individual.addressInformation; + if (addressInformation && addressInformation.addressLine1 && addressInformation.addressLine1.length > 1) { + return true; + } + return (individual.hasSameAddressAsSpouse === CreditApplicationValues.CHOICE_YES); + } + + public individualHasValidPersonalInformation(individual: (Individual | TattleIndividual)): boolean { + const personalInformation = individual.personalInformation; + return !!(personalInformation && personalInformation.firstName && personalInformation.firstName.length > 1); + } + + public hasOptedOutTCPAValue(disclosureInformation: DisclosureInformation): boolean { + return !!(disclosureInformation && !disclosureInformation.contact); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/credit-card-translator.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/credit-card-translator.spec.ts new file mode 100644 index 0000000..b0704a1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/credit-card-translator.spec.ts @@ -0,0 +1,99 @@ +import { CreditCardTranslator } from './credit-card-translator'; +import { CreditApplication } from '../../credit-application'; +import { LoanTypeSet } from '../../loan-type'; +import { CreditApplicationValues } from '../../credit-application-values'; +import { ApiStatus } from '../../api-models/api-status'; +import { ApiError, ApiErrorCode } from '../../api-models/api-error'; +import { PhoneTypeEnum } from '../../api-models/api-enums'; + +describe('Credit Card Translator', () => { + + let service: CreditCardTranslator; + let creditApplicationStub: CreditApplication; + + beforeEach(() => { + service = new CreditCardTranslator(); + creditApplicationStub = new CreditApplication(LoanTypeSet[0]); + + creditApplicationStub = { + primaryContactMethod: '1234567890', + limitRequested: 5446, + loanType: LoanTypeSet[1], + hasBeenCompleted: false, + disclosureInformation: { + contact: true, + truthful: true + }, + hasCoApplicant: CreditApplicationValues.CHOICE_YES, + hasCoApplicantSpouse: CreditApplicationValues.CHOICE_NO, + fullInfoBorrowers: [{ + personalInformation: { + firstName: 'Frank', + lastName: 'Reynolds', + middleInitial: 'M', + dateOfBirth: '09-17-1991', + socialSecurityNumber: '999-88-7777', + primaryPhoneNumber: '(414) 439-0116', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + email: 'developer@git.com', + secondaryPhoneNumber: '(414) 867-5309', + secondaryPhoneNumberType: PhoneTypeEnum.Home + }, + addressInformation: { + addressLine1: '51 Gardner', + city: 'Alston', + state: 'MA', + zipcode: '54545' + }, + residenceInformation: { + residenceTimeMonths: '2', + residenceTimeYears: '3', + residenceStatus: '1', + residenceMonthlyPayment: 5445, + isMailingAddressSame: true + } + }], + status: ApiStatus.IN_PROGRESS, + error: { + code: ApiErrorCode.No_Error, + message: 'No Errors' + }, + page: { + hasAddress: true, + hasEmployment: true, + hasPersonal: true, + hasMemberInfo: true, + hasSpouseInfo: true, + hasTattleSpouseInfo: true, + hasResidenceInfo: true, + hasVehicleCollateral: true, + hasHMDA: true, + hasDisclosure: true, + hasCollateralInfo: true, + hasNote: true + }, + }; + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should generate top level ApiLoanApplication fields', () => { + const topLevelGroup = service.createApiLoanHeader(creditApplicationStub); + expect(topLevelGroup.requestedAmount).toBe(5446); + expect(topLevelGroup.purpose).toBe(LoanTypeSet[1].defaultPurpose); + expect(topLevelGroup.loanType).toBe(LoanTypeSet[1].apiCode); + }); + + it('should return null value with no collateral information', () => { + const result = service.createCollateralFields(creditApplicationStub); + expect(result).toBe(null); + }); + + it('should return null value with no vehicle information', () => { + const result = service.createVehicleFields(creditApplicationStub); + expect(result).toBe(null); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/credit-card-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/credit-card-translator.ts new file mode 100644 index 0000000..2fd76cd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/credit-card-translator.ts @@ -0,0 +1,32 @@ +import { HeaderTranslator } from '../header-translator'; +import { CreditApplication } from '../../credit-application'; +import { MaskingService } from '../../masking/masking.service'; + +export class CreditCardTranslator implements HeaderTranslator { + + public createApiLoanHeader(creditApplication: CreditApplication): any { + return { + loanType: creditApplication.loanType.apiCode, + requestedAmount: MaskingService.unMaskNumber(creditApplication.limitRequested), + purpose: creditApplication.loanType.defaultPurpose, + note: this.createNote(creditApplication), + primaryContactMethod: creditApplication.primaryContactMethod, + promoCode: creditApplication.promoCode, + disclosureDate: creditApplication.disclosureInformation.date, + loanTitle: creditApplication.loanType.title, + clientMessage: creditApplication.clientMessage + }; + } + + public createCollateralFields(creditApplication: CreditApplication): any { + return null; + } + + public createVehicleFields(creditApplication: CreditApplication): any { + return null; + } + + public createNote(creditApplication: CreditApplication): string { + return creditApplication.note; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/equity-translator.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/equity-translator.spec.ts new file mode 100644 index 0000000..36df246 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/equity-translator.spec.ts @@ -0,0 +1,247 @@ +import { CreditCardTranslator } from './credit-card-translator'; +import { CreditApplication, CollateralInformationGroup, LienPosition } from '../../credit-application'; +import { LoanType, LoanTypeSet } from '../../loan-type'; +import { CreditApplicationValues } from '../../credit-application-values'; +import { CollateralInformation } from '../../../loan-wizard/field-groups/equity/collateral-information'; +import { AddressInformation } from '../../../loan-wizard/field-groups/address/address-information'; +import { ApiRealEstate } from '../../api-models/api-real-estate'; +import { EquityTranslator } from './equity-translator'; +import { ApiStatus } from '../../api-models/api-status'; +import { ApiErrorCode } from '../../api-models/api-error'; +import { PhoneTypeEnum } from '../../api-models/api-enums'; +import { LoanPurpose } from '../../loan-type/loan-purpose'; +import { LoanTitle } from '../../loan-type/loan-title'; +import { LoanCode } from '../../loan-type/loan-code'; + +describe('Equity Translator', () => { + + let service: EquityTranslator; + let creditApplicationStub: CreditApplication; + + beforeEach(() => { + service = new EquityTranslator(); + creditApplicationStub = { + primaryContactMethod: '1234567890', + limitRequested: 5446, + loanType: LoanTypeSet[4], + hasBeenCompleted: false, + disclosureInformation: { + contact: true, + truthful: true, + date: new Date() + }, + purposeOfLoan: LoanTypeSet[4].defaultPurpose, + hasCoApplicant: CreditApplicationValues.CHOICE_YES, + hasCoApplicantSpouse: CreditApplicationValues.CHOICE_NO, + fullInfoBorrowers: [{ + personalInformation: { + firstName: 'Frank', + lastName: 'Reynolds', + middleInitial: 'M', + dateOfBirth: '09-17-1991', + socialSecurityNumber: '999-88-7777', + primaryPhoneNumber: '(414) 439-0116', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + email: 'developer@git.com', + secondaryPhoneNumber: '(414) 867-5309', + secondaryPhoneNumberType: PhoneTypeEnum.Home + }, + addressInformation: { + addressLine1: '51 Gardner', + city: 'Alston', + state: 'MA', + zipcode: '54545' + }, + residenceInformation: { + residenceTimeMonths: '2', + residenceTimeYears: '3', + residenceStatus: '1', + residenceMonthlyPayment: 5445, + isMailingAddressSame: true + } + }], + status: ApiStatus.IN_PROGRESS, + error: { + code: ApiErrorCode.No_Error, + message: 'No Errors' + }, + page: { + hasAddress: true, + hasEmployment: true, + hasPersonal: true, + hasMemberInfo: true, + hasSpouseInfo: true, + hasTattleSpouseInfo: true, + hasResidenceInfo: true, + hasVehicleCollateral: true, + hasHMDA: true, + hasDisclosure: true, + hasCollateralInfo: true, + hasNote: true + }, + }; + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should generate top level ApiLoanApplication fields', () => { + const topLevelGroup = service.createApiLoanHeader(creditApplicationStub); + expect(topLevelGroup.requestedAmount).toBe(5446); + expect(topLevelGroup.purpose).toBe(LoanTypeSet[4].defaultPurpose); + expect(topLevelGroup.loanType).toBe(LoanTypeSet[4].apiCode); + }); + + it('should generate ApiRealEstate from collateral information', () => { + const collateralData: CollateralInformation = { + estimatedValue: 100000, + propertyType: 'Townhouse', + isBeingImproved: CreditApplicationValues.CHOICE_YES, + isListedForSale: CreditApplicationValues.CHOICE_NO, + occupancyType: 'Rental' + }; + const addressData: AddressInformation = { + addressLine1: '6 Spooner St.', + city: 'Quohog', + state: 'RI', + zipcode: '53207' + }; + Object.assign(creditApplicationStub, {collateralInformation: Object.assign({}, addressData, collateralData)}); + const apiRealEstate: ApiRealEstate = service.createCollateralFields(creditApplicationStub).realEstate; + expect(apiRealEstate).toBeTruthy(); + expect(apiRealEstate.type).toBe('Townhouse'); + expect(apiRealEstate.use).toBe('Rental'); + }); + + it('should return null value with no collateral information', () => { + const result = service.createCollateralFields(creditApplicationStub); + expect(result).toBe(null); + }); + + it('should return null value with no vehicle information', () => { + const result = service.createVehicleFields(creditApplicationStub); + expect(result).toBe(null); + }); + + it('should return the correct purpose for a Mfd Home purchase', () => { + const manufacturedHomeType = (LoanTypeSet.find((loanType) => loanType.title === LoanTitle.ManufacturedHome) || + ({ id: 'mock-mh', apiCode: LoanCode.MANUFACTURED_HOME_LOAN, defaultPurpose: LoanPurpose.Home_Purchase, title: LoanTitle.ManufacturedHome } as LoanType)); + Object.assign(creditApplicationStub, { + loanType: manufacturedHomeType, + purposeOfLoan: LoanPurpose.Home_Purchase + }); + + const apiHeader = service.createApiLoanHeader(creditApplicationStub); + expect(apiHeader.purpose).toEqual(LoanPurpose.Purchase_Manufactured_Home); + }); + + it('should create correct JSON for Manufactured Housing', () => { + const manufacturedHomeType = (LoanTypeSet.find((loanType) => loanType.title === LoanTitle.ManufacturedHome) || + ({ id: 'mock-mh', apiCode: LoanCode.MANUFACTURED_HOME_LOAN, defaultPurpose: LoanPurpose.Home_Purchase, title: LoanTitle.ManufacturedHome } as LoanType)); + Object.assign(creditApplicationStub, { + loanType: manufacturedHomeType, + purposeOfLoan: manufacturedHomeType.defaultPurpose, + }); + const collateralData: CollateralInformation = { + estimatedValue: 250000, + propertyType: 'Townhouse', + isBeingImproved: CreditApplicationValues.CHOICE_YES, + isListedForSale: CreditApplicationValues.CHOICE_NO, + occupancyType: 'Rental', + onRentedLot: CreditApplicationValues.CHOICE_YES, + rentPaid: '123.45', + annualPropertyTax: '5000.00' + }; + const addressData: AddressInformation = { + addressLine1: '123 W Main St', + city: 'Milwaukee', + state: 'WI', + zipcode: '53202' + }; + + Object.assign(creditApplicationStub, {collateralInformation: Object.assign({}, addressData, collateralData)}); + const apiRealEstate: ApiRealEstate = service.createCollateralFields(creditApplicationStub).manufacturedHome; + + const manufacturedHomeApiJson = { + 'type': 'Townhouse', + 'use': 'Rental', + 'currentlyBeingRemodeledOrImproved': true, + 'currentlyListedForSale': false, + 'estimatedHomeValuation': 250000, + 'lot': { + 'country': 'UnitedStates', + 'address1': '123 W Main St', + 'address2': null, + 'city': 'Milwaukee', + 'state': 'WI', + 'postalCode': '53202', + 'rentalAmt': '123.45', + 'isRented': true, + 'postalCodeExt': null, + 'annualPropertyTax': '5000.00' + }, + }; + expect(apiRealEstate).toEqual(manufacturedHomeApiJson); + + }); + + + it('should default isListedForSale to false if it IS a heloc rate mod loan', () => { + + const collateralData = { + estimatedValue: 100000, + propertyType: 'Townhouse', + isBeingImproved: 'no', + isListedForSale: CreditApplicationValues.CHOICE_YES, + occupancyType: 'Rental', + hasFirstMortgage: CreditApplicationValues.CHOICE_NO, + hasSecondMortgage: CreditApplicationValues.CHOICE_NO, + payoffFirstMortgage: CreditApplicationValues.CHOICE_NO, + payoffSecondMortgage: CreditApplicationValues.CHOICE_NO, + }; + + const result = service.generateRealEstateCollateralFields(collateralData, true); + + expect(result.currentlyListedForSale).toBe(false); + }); + + it('should default isListedForSale to true if it IS NOT a heloc rate mod loan', () => { + + const collateralData = { + estimatedValue: 100000, + propertyType: 'Townhouse', + isBeingImproved: 'no', + isListedForSale: CreditApplicationValues.CHOICE_YES, + occupancyType: 'Rental', + hasFirstMortgage: CreditApplicationValues.CHOICE_NO, + hasSecondMortgage: CreditApplicationValues.CHOICE_NO, + payoffFirstMortgage: CreditApplicationValues.CHOICE_NO, + payoffSecondMortgage: CreditApplicationValues.CHOICE_NO, + }; + + const result = service.generateRealEstateCollateralFields(collateralData, false); + + expect(result.currentlyListedForSale).toBe(true); + }); + + it('should default isListedForSale to false if it IS a mfg home loan', () => { + + const collateralData = { + estimatedValue: 100000, + propertyType: 'Townhouse', + isBeingImproved: 'no', + isListedForSale: CreditApplicationValues.CHOICE_YES, + occupancyType: 'Rental', + hasFirstMortgage: CreditApplicationValues.CHOICE_NO, + hasSecondMortgage: CreditApplicationValues.CHOICE_NO, + payoffFirstMortgage: CreditApplicationValues.CHOICE_NO, + payoffSecondMortgage: CreditApplicationValues.CHOICE_NO, + }; + + const result = service.generateMfgHome(collateralData); + + expect(result.currentlyListedForSale).toBe(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/equity-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/equity-translator.ts new file mode 100644 index 0000000..c3a3f67 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/equity-translator.ts @@ -0,0 +1,100 @@ +import { HeaderTranslator } from '../header-translator'; +import { CreditApplication } from '../../credit-application'; +import { ApiManufacturedHome } from '../../api-models/api-manufactured-home'; +import { ApiPropertyLot } from '../../api-models/api-lot'; +import { ApiAddress } from '../../api-models/api-address'; +import { ApiRealEstate } from '../../api-models/api-real-estate'; +import { AddressTranslator } from '../elements/address-translator'; +import { CreditApplicationValues } from '../../credit-application-values'; +import { LoanCode } from '../../loan-type/loan-code'; +import { LoanPurpose } from '../../loan-type/loan-purpose'; + +export class EquityTranslator implements HeaderTranslator { + + public createApiLoanHeader(creditApplication: CreditApplication): any { + if (!creditApplication.purposeOfLoan) { + throw new Error('Purpose of loan is required in Equity applications'); + } + + if (creditApplication.loanType.apiCode === LoanCode.MANUFACTURED_HOME_LOAN && + creditApplication.purposeOfLoan === LoanPurpose.Home_Purchase) { + // Manufactured Home Loan has its own special purchase purpose + creditApplication.purposeOfLoan = LoanPurpose.Purchase_Manufactured_Home; + } + + return { + loanType: creditApplication.loanType.apiCode, + requestedAmount: creditApplication.limitRequested, + purpose: creditApplication.purposeOfLoan, + termLengthInMonths: creditApplication.termOfLoan, + note: this.createNote(creditApplication), + primaryContactMethod: creditApplication.primaryContactMethod, + promoCode: creditApplication.promoCode, + disclosureDate: creditApplication.disclosureInformation.date, + loanTitle: creditApplication.loanType.title, + clientMessage: creditApplication.clientMessage, + modFeeAccount: creditApplication.modFeeAccount + }; + } + + public createCollateralFields(creditApplication: CreditApplication): any { + if (creditApplication.collateralInformation) { + if (creditApplication.collateralInformation.isAddressSame === CreditApplicationValues.CHOICE_YES) { + Object.assign(creditApplication.collateralInformation, creditApplication.fullInfoBorrowers[0].addressInformation); + } + + if (creditApplication.loanType.apiCode === LoanCode.MANUFACTURED_HOME_LOAN) { + return {manufacturedHome: this.generateMfgHome(creditApplication.collateralInformation)}; + } else { + return {realEstate: this.generateRealEstateCollateralFields(creditApplication.collateralInformation, creditApplication.loanType.apiCode === LoanCode.EQ_HELOC_Rate_Mod)}; + } + } + return null; + } + + public createVehicleFields(creditApplication: CreditApplication): any { + return null; + } + + public generateMfgHome(collateralData): ApiManufacturedHome { + return { + type: collateralData.propertyType, + use: collateralData.occupancyType, + currentlyListedForSale: false, + currentlyBeingRemodeledOrImproved: collateralData.isBeingImproved === CreditApplicationValues.CHOICE_YES, + estimatedHomeValuation: collateralData.estimatedValue, + lot: this.generateLotForMfgHome(collateralData) + }; + } + + public generateLotForMfgHome(lotInfo): ApiPropertyLot { + const address: ApiAddress = AddressTranslator.generate(lotInfo); + const extendedFields = { + isRented: !!lotInfo.rentPaid, + rentalAmt: lotInfo.rentPaid, + annualPropertyTax: lotInfo.annualPropertyTax + }; + return {...address, ...extendedFields}; + } + + public generateRealEstateCollateralFields(collateralData, isHelocRateModLoan): ApiRealEstate { + return { + initialEstimatedValue: collateralData.estimatedValue, + type: collateralData.propertyType, + use: collateralData.occupancyType, + realEstateAddress: AddressTranslator.generate(collateralData), + currentlyListedForSale: !isHelocRateModLoan && collateralData.isListedForSale === CreditApplicationValues.CHOICE_YES, + currentlyBeingRemodeledOrImproved: collateralData.isBeingImproved === CreditApplicationValues.CHOICE_YES, + lienPosition : { + hasFirstMortgage : collateralData.hasFirstMortgage === CreditApplicationValues.CHOICE_YES, + hasSecondMortgage: collateralData.hasSecondMortgage === CreditApplicationValues.CHOICE_YES, + payoffFirstMortgage: collateralData.payoffFirstMortgage ? true : false, + payoffSecondMortgage: collateralData.payoffSecondMortgage ? true : false, + } + }; + } + + public createNote(creditApplication: CreditApplication): string { + return creditApplication.note; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/signature-loan-translator.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/signature-loan-translator.spec.ts new file mode 100644 index 0000000..896f069 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/signature-loan-translator.spec.ts @@ -0,0 +1,107 @@ +import { CreditApplication } from '../../credit-application'; +import { LoanType, LoanTypeSet } from '../../loan-type'; +import { LoanTitle } from '../../loan-type/loan-title'; +import { LoanCode } from '../../loan-type/loan-code'; +import { LoanPurpose } from '../../loan-type/loan-purpose'; +import { CreditApplicationValues } from '../../credit-application-values'; +import { SignatureLoanTranslator } from './signature-loan-translator'; +import { ApiStatus } from '../../api-models/api-status'; +import { ApiError, ApiErrorCode } from '../../api-models/api-error'; +import { PhoneTypeEnum } from '../../api-models/api-enums'; + +describe('Signature Loan Translator', () => { + + let service: SignatureLoanTranslator; + let creditApplicationStub: CreditApplication; + + beforeEach(() => { + service = new SignatureLoanTranslator(); + const signatureType = LoanTypeSet.find((loanType) => loanType.title === LoanTitle.Signature) as LoanType; + creditApplicationStub = new CreditApplication(signatureType || LoanTypeSet[0]); + + creditApplicationStub = { + primaryContactMethod: '1234567890', + limitRequested: 5446, + termOfLoan: '12', + loanType: signatureType || LoanTypeSet[0], + hasBeenCompleted: false, + disclosureInformation: { + contact: true, + truthful: true + }, + hasCoApplicant: CreditApplicationValues.CHOICE_YES, + hasCoApplicantSpouse: CreditApplicationValues.CHOICE_NO, + fullInfoBorrowers: [{ + personalInformation: { + firstName: 'Frank', + lastName: 'Reynolds', + middleInitial: 'M', + dateOfBirth: '09-17-1991', + socialSecurityNumber: '999-88-7777', + primaryPhoneNumber: '(414) 439-0116', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + email: 'developer@git.com', + secondaryPhoneNumber: '(414) 867-5309', + secondaryPhoneNumberType: PhoneTypeEnum.Home + }, + addressInformation: { + addressLine1: '51 Gardner', + city: 'Alston', + state: 'MA', + zipcode: '54545' + }, + residenceInformation: { + residenceTimeMonths: '2', + residenceTimeYears: '3', + residenceStatus: '1', + residenceMonthlyPayment: 5445, + isMailingAddressSame: true + } + }], + status: ApiStatus.IN_PROGRESS, + error: { + code: ApiErrorCode.No_Error, + message: 'No Errors' + }, + page: { + hasAddress: true, + hasEmployment: true, + hasPersonal: true, + hasMemberInfo: true, + hasSpouseInfo: true, + hasTattleSpouseInfo: true, + hasResidenceInfo: true, + hasVehicleCollateral: true, + hasHMDA: true, + hasDisclosure: true, + hasCollateralInfo: true, + hasNote: true + }, + }; + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should generate top level ApiLoanApplication fields', () => { + const signatureType = LoanTypeSet.find((loanType) => loanType.title === LoanTitle.Signature) as LoanType; + const workingCreditApplication = signatureType ? creditApplicationStub : { ...creditApplicationStub, loanType: { ...creditApplicationStub.loanType, apiCode: LoanCode.SIGNATURE, defaultPurpose: LoanPurpose.Other } }; + const topLevelGroup = service.createApiLoanHeader(workingCreditApplication); + expect(topLevelGroup.requestedAmount).toBe(5446); + expect(topLevelGroup.purpose).toBe((workingCreditApplication.loanType.defaultPurpose)); + expect(topLevelGroup.loanType).toBe(workingCreditApplication.loanType.apiCode); + expect(topLevelGroup.termLengthInMonths).toBe(12); + }); + + it('should return null value with no collateral information', () => { + const result = service.createCollateralFields(creditApplicationStub); + expect(result).toBe(null); + }); + + it('should return null value with no vehicle information', () => { + const result = service.createVehicleFields(creditApplicationStub); + expect(result).toBe(null); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/signature-loan-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/signature-loan-translator.ts new file mode 100644 index 0000000..99d117a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/signature-loan-translator.ts @@ -0,0 +1,34 @@ +import { HeaderTranslator } from '../header-translator'; +import { CreditApplication } from '../../credit-application'; +import { MaskingService } from '../../masking/masking.service'; + +export class SignatureLoanTranslator implements HeaderTranslator { + + public createApiLoanHeader(creditApplication: CreditApplication): any { + return { + loanType: creditApplication.loanType.apiCode, + requestedAmount: MaskingService.unMaskNumber(creditApplication.limitRequested), + purpose: creditApplication.loanType.defaultPurpose, + purposeOfLoanSignature: creditApplication.purposeOfLoanSignature, + note: this.createNote(creditApplication), + primaryContactMethod: creditApplication.primaryContactMethod, + promoCode: creditApplication.promoCode, + disclosureDate: creditApplication.disclosureInformation.date, + termLengthInMonths: parseInt(creditApplication.termOfLoan, 10), + loanTitle: creditApplication.loanType.title, + clientMessage: creditApplication.clientMessage + }; + } + + public createCollateralFields(creditApplication: CreditApplication): any { + return null; + } + + public createVehicleFields(creditApplication: CreditApplication): any { + return null; + } + + public createNote(creditApplication: CreditApplication): string { + return creditApplication.note; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/vehicle-translator.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/vehicle-translator.spec.ts new file mode 100644 index 0000000..9e3cf05 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/vehicle-translator.spec.ts @@ -0,0 +1,180 @@ +import { CreditApplication } from '../../credit-application'; +import { LoanTypeSet } from '../../loan-type'; +import { CreditApplicationValues } from '../../credit-application-values'; +import { VehicleTranslator } from './vehicle-translator'; +import { ApiVehicle } from '../../api-models/api-vehicle'; +import { ApiStatus } from '../../api-models/api-status'; +import { ApiErrorCode } from '../../api-models/api-error'; +import { PhoneTypeEnum } from '../../api-models/api-enums'; +import { VehicleType } from '../../vehicle-type/vehicle-type'; +import { LoanPurpose } from '../../loan-type/loan-purpose'; + +describe('Vehicle Translator', () => { + + let service: VehicleTranslator; + let creditApplicationStub: CreditApplication; + + beforeEach(() => { + service = new VehicleTranslator(); + creditApplicationStub = new CreditApplication(LoanTypeSet[0]); + + creditApplicationStub = { + primaryContactMethod: '1234567890', + limitRequested: 5446, + loanType: LoanTypeSet[1], + hasBeenCompleted: false, + purposeOfLoan: LoanPurpose.Purchase, + disclosureInformation: { + contact: true, + truthful: true + }, + hasCoApplicant: CreditApplicationValues.CHOICE_YES, + hasCoApplicantSpouse: CreditApplicationValues.CHOICE_NO, + fullInfoBorrowers: [{ + personalInformation: { + firstName: 'Frank', + lastName: 'Reynolds', + middleInitial: 'M', + dateOfBirth: '09-17-1991', + socialSecurityNumber: '999-88-7777', + primaryPhoneNumber: '(414) 439-0116', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + email: 'developer@git.com', + secondaryPhoneNumber: '(414) 867-5309', + secondaryPhoneNumberType: PhoneTypeEnum.Home + }, + addressInformation: { + addressLine1: '51 Gardner', + city: 'Alston', + state: 'MA', + zipcode: '54545' + }, + residenceInformation: { + residenceTimeMonths: '2', + residenceTimeYears: '3', + residenceStatus: '1', + residenceMonthlyPayment: 5445, + isMailingAddressSame: true + } + }], + vehicleInformation: { + value: 12000, + type: VehicleType.AUTO, + condition: 'Used', + year: 2015, + make: 'Honda', + model: 'Fit', + mileage: 69000, + term: '36', + loanPurpose: 'Other', + seller: 'Dealer', + vin: '123456789', + licensePlateNumber: 'Plate#1234', + dealerName: 'Dealer Test', + dealerPhone: '9999999999', + stateTitled: 'WI', + countyTitled: 'Washington', + titleBranded: true, + lien: false + }, + status: ApiStatus.IN_PROGRESS, + error: { + code: ApiErrorCode.No_Error, + message: 'No Errors' + }, + page: { + hasAddress: true, + hasEmployment: true, + hasPersonal: true, + hasMemberInfo: true, + hasSpouseInfo: true, + hasTattleSpouseInfo: true, + hasResidenceInfo: true, + hasVehicleCollateral: true, + hasHMDA: true, + hasDisclosure: true, + hasCollateralInfo: true, + hasNote: true + }, + }; + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should generate top level ApiLoanApplication fields', () => { + const topLevelGroup = service.createApiLoanHeader(creditApplicationStub); + expect(topLevelGroup.requestedAmount).toBe(5446); + }); + + it('should generate top level fields for vehicle information', () => { + + const vehicleTopLevelGroup = service.createApiLoanHeader(creditApplicationStub); + const vehicleGroup = Object.assign({}, service.generateVehicleInformationFields(creditApplicationStub)); + expect(vehicleTopLevelGroup.purpose).toBe('Other'); + expect(vehicleGroup.initialEstimatedValue).toEqual(12000); + expect(vehicleGroup.make).toBe('Honda'); + + const vehicleApiJson = { + 'valuationType': 'Manual', + 'condition': 'Used', + 'seller': 'Dealer', + 'type': 'Automobile', + 'initialEstimatedValue': 12000, + 'year': 2015, + 'mileage': 69000, + 'make': 'Honda', + 'model': 'Fit', + 'vin': '123456789', + 'licensePlateNumber': 'Plate#1234', + 'dealerName': 'Dealer Test', + 'dealerPhone': '9999999999', + 'stateTitled': 'WI', + 'countyTitled': 'Washington', + 'titleBranded': true, + 'lien': false, + 'boatLength': undefined, + 'boatSameAddress': undefined, + 'trailerYear': undefined, + 'trailerMake': undefined, + 'trailerType': undefined, + 'motorYear': undefined, + 'motorMake': undefined, + 'motorModel': undefined, + 'motorType': undefined + }; + + expect(vehicleGroup).toEqual(vehicleApiJson); + }); + + it('should generate correct header purpose (boat loan)', () => { + spyOn(service, 'createApiLoanHeader').and.callThrough(); + const customCreditAppStub = creditApplicationStub; + customCreditAppStub.loanType = LoanTypeSet[11]; + customCreditAppStub.vehicleInformation.loanPurpose = null; + const vehicleTopLevelGroup = service.createApiLoanHeader(customCreditAppStub); + expect(service.createApiLoanHeader).toHaveBeenCalled(); + expect(vehicleTopLevelGroup.purpose).toBe('Purchase'); + }); + + it('should throw error for loan with no purpose header (boat loan)', () => { + spyOn(service, 'createApiLoanHeader').and.callThrough(); + const customCreditAppStub = creditApplicationStub; + customCreditAppStub.loanType.defaultPurpose = null; + service.createApiLoanHeader(customCreditAppStub); + expect(service.createApiLoanHeader).toHaveBeenCalled(); + expect(service.createApiLoanHeader).toThrowError(); + }); + + it('should return null value with no collateral information', () => { + const result = service.createCollateralFields(creditApplicationStub); + expect(result).toBe(null); + }); + + it('should return null value with no vehicle information', () => { + creditApplicationStub.vehicleInformation = null; + const result = service.createCollateralFields(creditApplicationStub); + expect(result).toBe(null); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/vehicle-translator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/vehicle-translator.ts new file mode 100644 index 0000000..89b3938 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/api-translator/loan-type-translators/vehicle-translator.ts @@ -0,0 +1,108 @@ +import { HeaderTranslator } from '../header-translator'; +import { CreditApplication } from '../../credit-application';import { ApiVehicle } from '../../api-models/api-vehicle'; +import { ValuationTypeEnum } from '../../api-models/api-enums'; +import { ApiAddress } from '../../api-models/api-address'; +import { AddressInformation } from 'src/app/loan-wizard/field-groups/address/address-information'; +import { MaskingService } from '../../masking/masking.service'; + +export class VehicleTranslator implements HeaderTranslator { + + // Currently, sending manual all of the time, is fine. This may change. + public static DEFAULT_VALUATION_TYPE = ValuationTypeEnum[6]; + + public createApiLoanHeader(creditApplication: CreditApplication): any { + if (!creditApplication.purposeOfLoan && !creditApplication.vehicleInformation.loanPurpose) { + throw new Error('Purpose of loan is required in Vehicle applications'); + } + + return { + loanType: creditApplication.loanType.apiCode, + termLengthInMonths: creditApplication.vehicleInformation ? creditApplication.vehicleInformation.term : creditApplication.termOfLoan, + purpose: this.setPurpose(creditApplication), + requestedAmount: creditApplication.limitRequested, + note: this.createNote(creditApplication), + primaryContactMethod: creditApplication.primaryContactMethod, + disclosureDate: creditApplication.disclosureInformation.date, + promoCode: creditApplication.promoCode, + loanTitle: creditApplication.loanType.title, + clientMessage: creditApplication.clientMessage + }; + } + + public createCollateralFields(creditApplication: CreditApplication): any { + return null; + } + + public createVehicleFields(creditApplication: CreditApplication): any { + if (creditApplication.vehicleInformation) { + return {vehicle: this.generateVehicleInformationFields(creditApplication)}; + } + return null; + } + + public generateVehicleInformationFields(creditApplication: CreditApplication): ApiVehicle { + + const vehicleInfo = creditApplication.vehicleInformation; + const apiVehicle = new ApiVehicle(); + + apiVehicle.valuationType = VehicleTranslator.DEFAULT_VALUATION_TYPE; + apiVehicle.condition = vehicleInfo.condition; + apiVehicle.seller = vehicleInfo.seller; + apiVehicle.type = vehicleInfo.type; + apiVehicle.year = vehicleInfo.year; + apiVehicle.mileage = vehicleInfo.mileage; + apiVehicle.make = vehicleInfo.make; + apiVehicle.model = vehicleInfo.model; + apiVehicle.initialEstimatedValue = vehicleInfo.value; + apiVehicle.vin = vehicleInfo.vin; + apiVehicle.licensePlateNumber = vehicleInfo.licensePlateNumber; + apiVehicle.dealerName = vehicleInfo.dealerName; + apiVehicle.dealerPhone = vehicleInfo.dealerPhone; + apiVehicle.stateTitled = vehicleInfo.stateTitled; + apiVehicle.countyTitled = vehicleInfo.countyTitled; + apiVehicle.titleBranded = vehicleInfo.titleBranded; + apiVehicle.lien = vehicleInfo.lien; + apiVehicle.boatLength = MaskingService.unMaskNumber(vehicleInfo.boatLength), + apiVehicle.boatSameAddress = vehicleInfo.boatSameAddress; + apiVehicle.trailerYear = vehicleInfo.trailerYear; + apiVehicle.trailerMake = vehicleInfo.trailerMake; + apiVehicle.trailerType = vehicleInfo.trailerType; + apiVehicle.motorYear = vehicleInfo.motorYear; + apiVehicle.motorMake = vehicleInfo.motorMake; + apiVehicle.motorModel = vehicleInfo.motorModel; + apiVehicle.motorType = vehicleInfo.motorType; + apiVehicle.municipalityType = vehicleInfo.municipalityType; + apiVehicle.municipalityWhereKept = vehicleInfo.municipalityWhereKept; + if (vehicleInfo.boatAddress) apiVehicle.boatAddress = this.createApiAddress(vehicleInfo.boatAddress); + + return apiVehicle; + } + + public createNote(creditApplication: CreditApplication): string { + return creditApplication.note; + } + + public setPurpose(creditApplication: CreditApplication): string { + if (creditApplication.vehicleInformation && creditApplication.vehicleInformation.loanPurpose) { + return creditApplication.vehicleInformation.loanPurpose; + } else { + return creditApplication.purposeOfLoan; + } + } + + public createApiAddress(boatAddress: AddressInformation): ApiAddress { + if (boatAddress) { + const apiBoatAddress = new ApiAddress(); + + apiBoatAddress.address1 = boatAddress.addressLine1; + apiBoatAddress.address2 = boatAddress.addressLine2; + apiBoatAddress.city = boatAddress.city; + apiBoatAddress.state = boatAddress.state; + apiBoatAddress.postalCode = boatAddress.zipcode; + apiBoatAddress.country = 'UnitedStates'; + + return apiBoatAddress; + } + return; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.html new file mode 100644 index 0000000..60c9816 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.html @@ -0,0 +1,12 @@ +
+
DOWN.HEADER
+
+
+
+

DOWN.BODY_TOP

+ +
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.scss new file mode 100644 index 0000000..691fef9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.scss @@ -0,0 +1,18 @@ + +.loan-header { + font-family: 'Open Sans', sans-serif !important; + font-size:44px; + color:#012E50 +} + +.btn-home { + margin-top: 20px; +} + +.title-hr { + margin: 0 0 2.5em 0; + color: #B1B1B1; + height: 1px; + border: none; + background-color: #B1B1B1; +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.spec.ts new file mode 100644 index 0000000..98a7183 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { AppDownComponent } from './app-down-page.component'; +import { configureTestSuite } from 'ng-bullet'; + +describe('AppDownComponent', () => { + let component: AppDownComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [AppDownComponent] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AppDownComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.ts new file mode 100644 index 0000000..0dd4497 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-down/app-down-page.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-app-down-page', + templateUrl: './app-down-page.component.html', + styleUrls: ['./app-down-page.component.scss'] +}) + + +export class AppDownComponent { + + constructor() {} + + public commandHome() { + location.href = 'https://www.landmarkcu.com'; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader-translator.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader-translator.service.spec.ts new file mode 100644 index 0000000..8c68c96 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader-translator.service.spec.ts @@ -0,0 +1,95 @@ +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { AppLoaderTranslatorService } from './app-loader-translator.service'; +import { FormatService } from '../pipes/format.service'; +import { apiTranslatorPostTranslate, apiTranslatorPreTranslate } from '../../testing/shared-mocks/api-translator-pre-post'; +import { configureTestSuite } from 'ng-bullet'; +import { TestApiLoanContainer, TestTranslateCreditApplication } from 'src/app/testing/shared-mocks/test-api-loan-container'; +import { CreditApplicationValues } from '../credit-application-values'; + +describe('AppLoaderTranslatorService', () => { + + let service: AppLoaderTranslatorService; + let formatService: FormatService; + const mockApiApp = apiTranslatorPostTranslate; + const mockCredApp = apiTranslatorPreTranslate; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot() + ], + declarations: [], + providers: [ + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + service = TestBed.get( AppLoaderTranslatorService ); + formatService = TestBed.get( FormatService ); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should translate residence', () => { + spyOn(service, 'translateResidence').and.callThrough(); + const residence = service.translateResidence(mockApiApp.primaryBorrower); + expect(service.translateResidence).toHaveBeenCalled(); + expect(residence).toBeTruthy(); + }); + + it('should translate address', () => { + spyOn(service, 'translateAddress').and.callThrough(); + const address = service.translateAddress(mockApiApp.primaryBorrower.address); + expect(service.translateAddress).toHaveBeenCalled(); + expect(address).toEqual(mockCredApp.fullInfoBorrowers[0].addressInformation); + }); + + it('should translate employment', () => { + spyOn(service, 'translateEmployment').and.callThrough(); + const employment = service.translateEmployment(mockApiApp.primaryBorrower.employers[0]); + expect(service.translateEmployment).toHaveBeenCalled(); + expect(employment).toEqual(mockCredApp.fullInfoBorrowers[0].employmentInformation[0]); + }); + + it('should translate personal', () => { + spyOn(service, 'translatePersonal').and.callThrough(); + const personal = service.translatePersonal(mockApiApp.primaryBorrower); + expect(service.translatePersonal).toHaveBeenCalled(); + expect(personal).toBeTruthy(); + expect(personal.maritalStatus).toEqual(CreditApplicationValues.CHOICE_YES); + }); + + it('should translate borrower', () => { + spyOn(service, 'translateBorrower').and.callThrough(); + const borrower = service.translateBorrower(mockApiApp.primaryBorrower); + expect(service.translateBorrower).toHaveBeenCalled(); + expect(borrower).toBeTruthy(); + }); + + it('should translate the api application', () => { + spyOn(service, 'translateData').and.callThrough(); + const translatedApplication = service.translateData(TestApiLoanContainer); + expect(service.translateData).toHaveBeenCalled(); + expect(translatedApplication).toBeTruthy(); + // GJF having trouble with this line due to types. + // doesn't find the problem anyway. + // expect(translatedApplication.fullInfoBorrowers[0]).toEqual(TestTranslateCreditApplication.fullInfoBorrowers[0]); + expect(translatedApplication.fullInfoBorrowers[0].personalInformation.maritalStatus).toEqual(CreditApplicationValues.CHOICE_NO); + expect(translatedApplication.loanId).toEqual('12341234'); + }); + + it('should get membership information from application', () => { + spyOn(service, 'getMembershipInformation').and.callThrough(); + const membershipinfo = service.getMembershipInformation(mockApiApp); + expect(service.getMembershipInformation).toHaveBeenCalled(); + expect(membershipinfo).toBeTruthy(); + }); + + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader-translator.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader-translator.service.ts new file mode 100644 index 0000000..f04ed48 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader-translator.service.ts @@ -0,0 +1,385 @@ +import { ResidenceInformation } from '../../loan-wizard/field-groups/address/residence-information'; +import { AddressInformation } from '../../loan-wizard/field-groups/address/address-information'; +import { EmploymentInformation } from '../../loan-wizard/field-groups/employment/employment-information'; +import { PersonalInformation } from '../../loan-wizard/field-groups/personal/personal-information'; +import { AlimonyInformation } from '../../loan-wizard/field-groups/employment/alimony-information'; +import { IncomeInformation } from '../../loan-wizard/field-groups/employment/income-information'; +import { MembershipInformation } from '../../loan-wizard/field-groups/member/membership-information'; +import { CreditApplicationValues } from '../credit-application-values'; +import { CreditApplication, Individual, SpouseType, TattleIndividual, LienPosition } from '../credit-application'; +import { throwError } from 'rxjs'; +import { LoanType, LoanTypeSet } from '../loan-type'; +import { Injectable } from '@angular/core'; +import { FormatService } from '../pipes/format.service'; +import { ApiLoanApplication } from '../api-models/api-loan-application'; +import { ApiIndividual } from '../api-models/api-individual'; +import { ApiLoanContainer } from '../api-models/api-loan-container'; +import { AddressValidationFormatConverter } from '../address-validation/address-validation-format-converter'; +import { ApiIncome } from '../api-models/api-income'; +import { ApiTattleSpouse } from '../api-models/api-tattle-spouse'; +import { TattleSpousePersonalInformation } from 'src/app/loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-information'; +import { ResidenceTypeEnum, PhoneTypeEnum } from '../api-models/api-enums'; +import { LoanPurpose } from '../loan-type/loan-purpose'; +import { CollateralInformation } from 'src/app/loan-wizard/field-groups/equity/collateral-information'; +import { ApiLienPosition } from '../api-models/api-lien-position'; +import { ApiHMDA } from '../api-models/api-hmda'; +import { ApiAddress } from '../api-models/api-address'; +import { IsAddressSameInformation } from 'src/app/loan-wizard/field-groups/address/is-address-same-information'; +import { HMDA_RACES, HMDA_ETHNICITIES, HMDA_SEXES } from '../api-models/api-hmda-enums'; +import { VehicleCollateralInformation } from 'src/app/loan-wizard/field-groups/vehicle/vehicle-collateral-information'; +import { LoanCode } from '../loan-type/loan-code'; + +@Injectable({ + providedIn: 'root' +}) +export class AppLoaderTranslatorService { + constructor(private formatService: FormatService) {} + + public translateResidence(address): ResidenceInformation { + return { + residenceMonthlyPayment: parseInt(address.monthlyPayment, 10), + residenceStatus: ResidenceTypeEnum[address.residenceType], + residenceTimeMonths: (address.monthsResided % 12).toString(), + residenceTimeYears: (Math.floor(address.monthsResided / 12)).toString() + }; + } + + public translateAddress(address): AddressInformation { + return { + addressLine1: address.address1, + addressLine2: address.address2, + city: address.city, + state: address.state, + zipcode: address.postalCode + }; + } + + public translateEmployment(employer): EmploymentInformation { + return { + employerName: employer.name, + employmentType: employer.employmentType, + jobTitle: employer.title, + salary: employer.income.amountPerPeriod, + schedule: employer.income.frequency, + employmentLengthYears: Math.floor(employer.monthsEmployed / 12), + employmentLengthMonths: (employer.monthsEmployed % 12), + weeklyHours: employer.income.hoursPerWeek ? employer.income.hoursPerWeek : null, + workPhoneNumber: this.formatService.formatPhoneNumber(employer.workPhone) + }; + } + + public translatePersonal(borrower: ApiIndividual): PersonalInformation { + const personalInformation = { + dateOfBirth: this.formatService.formatDob(borrower.dob), + email: borrower.email, + firstName: borrower.firstName, + lastName: borrower.lastName, + maritalStatus: borrower.isMarried ? CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO, + middleInitial: borrower.middleInitial, + primaryPhoneNumber: this.formatService.formatPhoneNumber(borrower.phone), + primaryPhoneNumberType: PhoneTypeEnum[borrower.phoneType], + secondaryPhoneNumber: borrower.secondaryPhone ? this.formatService.formatPhoneNumber(borrower.secondaryPhone) : null, + secondaryPhoneNumberType: borrower.secondaryPhoneType ? PhoneTypeEnum[borrower.secondaryPhoneType] : '', + socialSecurityNumber: this.formatService.formatSsnNoStars(borrower.ssn), + primaryApplicantEmail: borrower.primaryApplicantEmail, + coApplicantEmail: borrower.coApplicantEmail + }; + return personalInformation; + } + + public translateTattlePersonal(tattleSpouse: ApiTattleSpouse): TattleSpousePersonalInformation { + return { + firstName: tattleSpouse.firstName, + middleInitial: tattleSpouse.middleInitial, + lastName: tattleSpouse.lastName + }; + } + + public translateAlimony(alimonyIncome: ApiIncome): AlimonyInformation { + return { + childSupportAmount: alimonyIncome.amountPerPeriod + }; + } + + public translateOtherIncome(income: ApiIncome): IncomeInformation { + return { + incomeName: income.sourceName, + frequency: income.frequency, + amount: income.amountPerPeriod + }; + } + + public translateTattleSpouse(tattleSpouse: ApiTattleSpouse): TattleIndividual { + return { + personalInformation: this.translateTattlePersonal(tattleSpouse), + addressInformation: AddressValidationFormatConverter.makeUiAddress(tattleSpouse.mailingAddress) + }; + } + + public translateBorrower(borrower: ApiIndividual): Individual { + const individual: Individual = new Individual(); + const personalInfo: PersonalInformation = this.translatePersonal(borrower); + const addressInfo: AddressInformation = this.translateAddress(borrower.address); + const residenceInfo: ResidenceInformation = this.translateResidence(borrower.address); + const sameMailingAddress = borrower.mailingAddress ? false : true; + residenceInfo.isMailingAddressSame = sameMailingAddress; + if (!sameMailingAddress) { + individual.mailingAddressInformation = this.translateAddress(borrower.mailingAddress); + } + + if (borrower.previousAddresses) { + const addressArray = []; + const residenceArray = []; + borrower.previousAddresses.forEach(prevAddresses => { + addressArray.push(this.translateAddress(prevAddresses)); + residenceArray.push(this.translateResidence(prevAddresses)); + }); + individual.priorAddressInformation = addressArray; + individual.priorResidenceInformation = residenceArray; + } + if (borrower.employers) { + const employersArray = []; + borrower.employers.forEach(employer => { + employersArray.push(this.translateEmployment(employer)); + }); + individual.employmentInformation = employersArray; + } + if (borrower.otherIncomes) { + individual.otherIncomeInformation = []; + borrower.otherIncomes.forEach((income: ApiIncome) => { + if (income.incomeTypeCD === 'Alimony') { + individual.alimonyInformation = this.translateAlimony(income); + } else { + individual.otherIncomeInformation.push(this.translateOtherIncome(income)); + } + }); + if (!individual.otherIncomeInformation.length) { + // @TODO: Just being safe here + individual.otherIncomeInformation = null; + } + } + individual.addressInformation = addressInfo; + individual.residenceInformation = residenceInfo; + individual.personalInformation = personalInfo; + + Object.assign(individual, this.getHMDAInformation(borrower.hmdaValues)); + + if (borrower.detailedNonApplicantSpouse) { + individual.fullInfoSpouse = this.translateBorrower(borrower.detailedNonApplicantSpouse); + individual.spouseType = SpouseType.FullInfoSpouse; + } else if (borrower.nonApplicantSpouse) { + const spouse = this.translateTattleSpouse(borrower.nonApplicantSpouse); + const diff = AddressValidationFormatConverter.getAddressValidationDiff( + borrower.address, spouse.addressInformation + ); + if (diff.allFieldsSame) { + spouse.hasSameAddressAsSpouse = CreditApplicationValues.CHOICE_YES; + } else { + spouse.hasSameAddressAsSpouse = CreditApplicationValues.CHOICE_NO; + } + individual.spouseType = SpouseType.TattleSpouse; + individual.tattleSpouse = spouse; + } + return individual; + } + + public translateData(container: ApiLoanContainer): CreditApplication { + const loanApp: ApiLoanApplication = container.loanApplication; + const loanType = this.getLoanTypeForAPIValue(loanApp.loanType); + const parsedCreditApp: CreditApplication = new CreditApplication(loanType); + // GJF, read the container id as the loan ID, same as creation. + parsedCreditApp.loanId = container.id; + + if (loanApp.primaryBorrower) { + parsedCreditApp.fullInfoBorrowers = []; + parsedCreditApp.fullInfoBorrowers.push(this.translateBorrower(loanApp.primaryBorrower)); + parsedCreditApp.hasCoApplicant = loanApp.coApplicant ? CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO; + if (loanApp.coApplicant) { + parsedCreditApp.fullInfoBorrowers.push(this.translateBorrower(loanApp.coApplicant)); + parsedCreditApp.hasCoApplicantSpouse = loanApp.coApplicant.isMarriedToPrimaryBorrower ? + CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO; + parsedCreditApp.confirmJointCredit = CreditApplicationValues.CHOICE_YES; + + const diff = AddressValidationFormatConverter.getAddressValidationDiff( + loanApp.coApplicant.address, parsedCreditApp.fullInfoBorrowers[0].addressInformation + ); + if (diff.allFieldsSame) { + parsedCreditApp.fullInfoBorrowers[1].hasSameAddressAsSpouse = CreditApplicationValues.CHOICE_YES; + } else { + parsedCreditApp.fullInfoBorrowers[1].hasSameAddressAsSpouse = CreditApplicationValues.CHOICE_NO; + } + } + } + + Object.assign(parsedCreditApp, this.getMembershipInformation(loanApp)); + Object.assign(parsedCreditApp, { + collateralInformation: this.getCollateralInformation(loanApp) + }); + Object.assign(parsedCreditApp, { + vehicleInformation: this.getVehicleCollateralInformation(loanApp) + }); + parsedCreditApp.limitRequested = loanApp.requestedAmount; + parsedCreditApp.purposeOfLoan = loanApp.purpose; + parsedCreditApp.purposeOfLoanSignature = loanApp.purposeOfLoanSignature; + if (!parsedCreditApp) { + throwError('unable to parse payload data'); + } + + return parsedCreditApp; + } + + public getLoanTypeForAPIValue(loanTypeApiCode: string): LoanType { + let foundLoanType = null; + LoanTypeSet.forEach((loanType, index) => { + if (loanType.apiCode === loanTypeApiCode) { + foundLoanType = loanType; + } + }, this); + return foundLoanType; + } + + public getMembershipInformation(application: ApiLoanApplication): MembershipInformation { + if (application && application.primaryBorrower) { + const memberInfo: MembershipInformation = { + applicantType: application.coApplicant ? CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO, + limitRequested: application.requestedAmount, + livesInMaritalPropertyState: application.primaryBorrower.address.state === CreditApplicationValues.STATE_REQUIRED_COAPP ? + CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO, + coApplicantLivesInMaritalPropertyState: application.coApplicant && application.coApplicant.address.state + === CreditApplicationValues.STATE_REQUIRED_COAPP ? + CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO, + purposeOfLoan: LoanPurpose[application.purpose], + purposeOfLoanSignature: application.purposeOfLoanSignature, + termOfLoan: application.termLengthInMonths + }; + return memberInfo; + } else { + return null; + } + } + + public getHMDAInformation(hmdaInfo: ApiHMDA) { + if (!hmdaInfo) { + return; + } + return { + ethnicityInformation: Object.assign( + this.parseHMDAInformation(hmdaInfo.ethnicities, HMDA_ETHNICITIES), {otherHispanicLatinoText: hmdaInfo.otherHispanicLatinoEthnicity} + ), + genderInformation: this.parseHMDAInformation(hmdaInfo.sexes, HMDA_SEXES), + raceInformation: Object.assign( + this.parseHMDAInformation(hmdaInfo.races, HMDA_RACES), + { + americanIndianAlaskaNativeText: hmdaInfo.americanIndianAlaskaNativeTribe, + otherAsianText: hmdaInfo.otherAsianRace, + otherPacificIslanderText: hmdaInfo.otherPacificIslanderRace + } + ), + }; + } + + + public parseHMDAInformation(hmdaValues: Array, enumGroup) { + const parsedValues = {}; + Object.keys(enumGroup).forEach(value => { + if (hmdaValues.indexOf(enumGroup[value]) > -1) { + Object.assign(parsedValues, {[value]: true}); + } + }); + + return parsedValues; + } + + public getVehicleCollateralInformation(application: ApiLoanApplication): VehicleCollateralInformation { + if (!application.vehicle) { + return; + } + return { + value: application.vehicle.initialEstimatedValue, + condition: application.vehicle.condition, + type: application.vehicle.type, + year: application.vehicle.year, + make: application.vehicle.make, + mileage: application.vehicle.mileage, + model: application.vehicle.model, + term: application.termLengthInMonths.toString(), + loanPurpose: application.purpose, + seller: application.vehicle.seller, + vin: application.vehicle.vin, + transferring: application.vehicle.transferring, + licensePlateNumber: application.vehicle.licensePlateNumber, + dealerName: application.vehicle.dealerName, + dealerPhone: application.vehicle.dealerPhone, + stateTitled: application.vehicle.stateTitled, + countyTitled: application.vehicle.countyTitled, + titleBranded: application.vehicle.titleBranded, + lien: application.vehicle.lien, + boatLength: application.vehicle.boatLength, + boatSameAddress: application.vehicle.boatSameAddress, + trailerYear: application.vehicle.trailerYear, + trailerMake: application.vehicle.trailerMake, + trailerType: application.vehicle.trailerType, + motorYear: application.vehicle.motorYear, + motorMake: application.vehicle.motorMake, + motorModel: application.vehicle.motorModel, + motorType: application.vehicle.motorType, + municipalityType: application.vehicle.municipalityType, + municipalityWhereKept: application.vehicle.municipalityWhereKept + }; + } + + public getCollateralInformation(application: ApiLoanApplication) { + if (!application.realEstate) { + return; + } + + let isListedForSale = application.realEstate.currentlyListedForSale ? + CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO; + + if(application.loanType === LoanCode.MANUFACTURED_HOME_LOAN || application.loanType === LoanCode.EQ_HELOC_Rate_Mod) { + isListedForSale = CreditApplicationValues.CHOICE_NO; + } + + const collateralInformation: CollateralInformation = { + estimatedValue: application.realEstate.initialEstimatedValue, + propertyType: application.realEstate.type, + isBeingImproved: application.realEstate.currentlyBeingRemodeledOrImproved ? + CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO, + isListedForSale, + occupancyType: application.realEstate.use + }; + + const realEstateAddress: AddressInformation = AddressValidationFormatConverter.makeUiAddress(application.realEstate.realEstateAddress); + const isAddressSame: IsAddressSameInformation = { + isAddressSame: AddressValidationFormatConverter.getAddressValidationDiff( + application.realEstate.realEstateAddress, + AddressValidationFormatConverter.makeUiAddress(application.primaryBorrower.address) + ).allFieldsSame ? CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO + }; + const lienPosition: LienPosition = this.getLienPosition(application.realEstate.lienPosition); + + return Object.assign( + collateralInformation, + realEstateAddress, + isAddressSame, + lienPosition); + } + + public getRealEstateAddress(address: ApiAddress): AddressInformation { + return AddressValidationFormatConverter.makeUiAddress(address); + } + + public getLienPosition(lienPosition: ApiLienPosition): LienPosition { + if (!lienPosition) { + return; + } + return { + hasFirstMortgage: lienPosition.hasFirstMortgage ? + CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO, + hasSecondMortgage: lienPosition.hasSecondMortgage ? + CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO, + payoffFirstMortgage: lienPosition.payoffFirstMortgage, + payoffSecondMortgage: lienPosition.payoffSecondMortgage + }; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.html new file mode 100644 index 0000000..418e184 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.html @@ -0,0 +1,19 @@ + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.scss new file mode 100644 index 0000000..8f8f08a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.scss @@ -0,0 +1,41 @@ +.list-header { + font-weight: bold; + margin-right: 5px; + color: #565656; +} +.list { + text-decoration: underline; + margin-right: 5px; +} + +ul#menu li { + display:inline; +} + +ul#menu { + padding-left: 0px; + margin-bottom: 0px; +} + +.btn-continue-loan { + text-decoration: underline; + border: 0px; + font-size: 1em; + padding-top: 0px; + padding-bottom: 0px; +} + +.btn-link { + color: #565656; +} + +.btn-link:hover { + color: #005B9A; +} + +/*mobile*/ +@media(max-width: 768px) { + ul#menu li { + display: block; + } +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.spec.ts new file mode 100644 index 0000000..c00a033 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.spec.ts @@ -0,0 +1,81 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { configureTestSuite } from 'ng-bullet'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { Router } from '@angular/router'; +import { of } from 'rxjs'; +import { MemberService } from 'src/app/member/member.service'; +import { LoanCodePipe } from 'src/app/shared/pipes/loan-code.pipe'; +import { TestApiLoanContainer } from 'src/app/testing/shared-mocks/test-api-loan-container'; +import { AppLoaderService } from './app-loader.service'; +import { AppLoaderComponent } from './app-loader.component'; +import { MemberMeta } from 'src/app/member/models/member-meta'; +import { LoanIconPipe } from '../pipes/loan-icon.pipe'; + +describe('SelectLoanComponent', () => { + let component: AppLoaderComponent; + let fixture: ComponentFixture; + let memberService: MemberService; + let appLoaderService: AppLoaderService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([]), + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot(), + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [ + AppLoaderComponent, + LoanCodePipe, + LoanIconPipe + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AppLoaderComponent); + component = fixture.componentInstance; + memberService = TestBed.get(MemberService); + appLoaderService = TestBed.get(AppLoaderService); + router = TestBed.get(Router); + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should intialize with default display state', () => { + expect(component.localState.display).toBe('empty'); + }); + + it('should create application and route user to loan app', () => { + spyOn(appLoaderService, 'createApplication'); + spyOn(router, 'navigateByUrl').and.returnValue(true); + component.commandContinueLoan(TestApiLoanContainer); + expect(component.localState.display).toBe('loading'); + expect(appLoaderService.createApplication).toHaveBeenCalled(); + expect(router.navigateByUrl).toHaveBeenCalledWith('/loan-wizard/cc/cb'); + }); + + it('should set display state to empty when user has no loans', () => { + component.updateLocalState([]); + expect(component.localState.display).toBe('empty'); + }); + + it('should set display state to loans when user has loans', () => { + component.updateLocalState([TestApiLoanContainer]); + expect(component.localState.display).toBe('loans'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.ts new file mode 100644 index 0000000..dcb5386 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { ApiLoanContainer } from 'src/app/shared/api-models/api-loan-container'; +import { RouteOptions } from 'src/app/shared/options/route-options'; +import { AppLoaderService } from 'src/app/shared/app-loader/app-loader.service'; +import { CookieUtil } from '../cookie/cookie-util'; + +export enum AppLoaderState { + LOADER_EMPTY = 'empty', + LOADER_LOANS = 'loans', + LOADER_LOADING = 'loading' +} + +@Component({ + selector: 'lw-app-loader-selector', + templateUrl: './app-loader.component.html', + styleUrls: ['./app-loader.component.scss'] +}) + +export class AppLoaderComponent implements OnInit { + public containers: ApiLoanContainer[]; + public localState = { + display: AppLoaderState.LOADER_EMPTY, + showLoanList: false + }; + + constructor(private router: Router, private appLoaderService: AppLoaderService) {} + + public ngOnInit() { + + if (CookieUtil.allowLoanResume()) { + this.localState.display = AppLoaderState.LOADER_LOADING; + this.localState.showLoanList = true; + this.appLoaderService.requestIndividualApplications().subscribe((loans) => { + this.updateLocalState(loans || []); + }, (error) => { + this.updateLocalState([]); + this.localState.display = AppLoaderState.LOADER_EMPTY; + }); + } + } + + public updateLocalState(loanContainers: Array): void { + this.containers = loanContainers; + if (this.containers.length < 1) { + this.localState.display = AppLoaderState.LOADER_EMPTY; + } else { + this.localState.display = AppLoaderState.LOADER_LOANS; + } + } + + public commandContinueLoan(loanContainer: ApiLoanContainer): void { + this.localState.display = AppLoaderState.LOADER_LOADING; + this.appLoaderService.createApplication(loanContainer); + this.router.navigateByUrl(RouteOptions.getRouteFromLoanCode(loanContainer.loanType)); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.service.spec.ts new file mode 100644 index 0000000..502fe4f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.service.spec.ts @@ -0,0 +1,55 @@ +import { AppLoaderService } from './app-loader.service'; +import { TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CreditApplicationDaoService } from '../credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../testing/shared-mocks/credit-application-dao.mock.service'; +import { CreditApplicationService } from '../credit-application.service'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { TestApiLoanContainer } from '../../testing/shared-mocks/test-api-loan-container'; +import { CreditApplicationValues } from '../credit-application-values'; +import { ApplicationWizardService } from '../../loan-wizard/application-wizard.service'; + +describe('AppLoaderService', () => { + + let service: AppLoaderService; + let appWizardService: ApplicationWizardService; + let creditAppService: CreditApplicationService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + NgbModule.forRoot(), + HttpClientTestingModule, + TranslateModule.forRoot() + ], + declarations: [], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + service = TestBed.get(AppLoaderService); + appWizardService = TestBed.get(ApplicationWizardService); + creditAppService = TestBed.get(CreditApplicationService); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create application', () => { + spyOn(appWizardService, 'setLoanType').and.callThrough(); + service.createApplication(TestApiLoanContainer); + expect(appWizardService.setLoanType).toHaveBeenCalled(); + const application = appWizardService.getFullCreditApplication(); + expect(application).toBeTruthy(); + expect(application.fullInfoBorrowers[0].personalInformation.maritalStatus).toEqual(CreditApplicationValues.CHOICE_NO); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.service.ts new file mode 100644 index 0000000..2fa1461 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-loader/app-loader.service.ts @@ -0,0 +1,49 @@ +import { ErrorHandler, Injectable } from '@angular/core'; +import { Observable, of, BehaviorSubject } from 'rxjs'; +import { catchError, map, delay } from 'rxjs/operators'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { ApplicationWizardService } from '../../loan-wizard/application-wizard.service'; +import { environment } from '../../../environments/environment'; +import { CreditApplication, Individual } from '../credit-application'; +import { AppLoaderTranslatorService } from './app-loader-translator.service'; +import { ApiLoanContainer } from '../api-models/api-loan-container'; +import { TestApiLoanContainer } from 'src/app/testing/shared-mocks/test-api-loan-container'; +import { MemberMeta } from 'src/app/member/models/member-meta'; +import { CreditApplicationService } from '../credit-application.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AppLoaderService { + + constructor( + private httpClient: HttpClient, + private applicationWizardService: ApplicationWizardService, + private translatorService: AppLoaderTranslatorService + ) { + + } + + public buildGetLoansURL(): string { + return environment.urlBase + '/Storage/GetMemberApplications'; + } + + public createApplication(container: ApiLoanContainer): void { + const parsedData: CreditApplication = this.translatorService.translateData(container); + this.applicationWizardService.setLoanType(parsedData.loanType.id, parsedData); + } + + public requestIndividualApplications(): Observable { + return this.httpClient.get(this.buildGetLoansURL(), {withCredentials: true}) + .pipe( + map((data: any) => { + return data; + }), + catchError((err, caught) => { + console.log(err); + return of([]); + }) + ); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.html new file mode 100644 index 0000000..f065b39 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.html @@ -0,0 +1,5 @@ +
+

UNAVAILABLE.HEADER

+
UNAVAILABLE.BODY_TOP
+
UNAVAILABLE.BODY_BOTTOM
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.spec.ts new file mode 100644 index 0000000..7d4c220 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { AppUnavailableComponent } from './app-unavailable-page.component'; +import { configureTestSuite } from 'ng-bullet'; + +describe('AppUnavailableComponent', () => { + let component: AppUnavailableComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [AppUnavailableComponent] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AppUnavailableComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.ts new file mode 100644 index 0000000..617c802 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/app-unavailable/app-unavailable-page.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'lw-app-unavailable-page', + templateUrl: './app-unavailable-page.component.html' +}) +export class AppUnavailableComponent { + + constructor() {} + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.html new file mode 100644 index 0000000..cb0d676 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.html @@ -0,0 +1,11 @@ +
+ +
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.spec.ts new file mode 100644 index 0000000..cee287d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.spec.ts @@ -0,0 +1,80 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { By } from "@angular/platform-browser"; +import { configureTestSuite } from 'ng-bullet'; +import { BeforeYouBeginComponent } from './before-you-begin.component'; + +describe('BeforeYouBeginComponent', () => { + let component: BeforeYouBeginComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + declarations: [ + BeforeYouBeginComponent, + ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(BeforeYouBeginComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should intialize with default message', () => { + expect(component.message).toBe(null); + }); + + it('should display intro message with list', () => { + component.message = { + "value": { + "INTRO": "Intro message", + "HEADINGS": [ + { + "HEADING": "Main header", + "VALUES": ["First value"] + } + ] + } + } + + fixture.detectChanges(); + + expect(fixture.debugElement.queryAll(By.css('span')).length).toBe(1); + expect(fixture.debugElement.queryAll(By.css('b')).length).toBe(1); + expect(fixture.debugElement.queryAll(By.css('ol')).length).toBe(1); + expect(fixture.debugElement.queryAll(By.css('li')).length).toBe(1); + + }); + + it('should display intro message with list', () => { + component.message = { + "value": { + "INTRO": "Intro message", + "HEADINGS": [ + { + "HEADING": "First header", + "VALUES": ["1", "2"] + }, + { + "HEADING": "Second header", + "VALUES": ["1", "2", "3"] + } + ] + } + } + + fixture.detectChanges(); + + expect(fixture.debugElement.queryAll(By.css('span')).length).toBe(1); + expect(fixture.debugElement.queryAll(By.css('b')).length).toBe(2); + expect(fixture.debugElement.queryAll(By.css('ol')).length).toBe(2); + expect(fixture.debugElement.queryAll(By.css('li')).length).toBe(5); + + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.ts new file mode 100644 index 0000000..324fdb3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/before-you-begin/before-you-begin.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; + +export enum AppLoaderState { + LOADER_EMPTY = 'empty', + LOADER_LOANS = 'loans', + LOADER_LOADING = 'loading' +} + +@Component({ + selector: 'lw-before-you-begin', + templateUrl: './before-you-begin.component.html' +}) + +export class BeforeYouBeginComponent { + + @Input() + public message: object = null; + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/borrower-type.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/borrower-type.ts new file mode 100644 index 0000000..60bad19 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/borrower-type.ts @@ -0,0 +1,7 @@ + +export enum BorrowerType { + PRIMARY_BORROWER = 'PRIMARY_BORROWER', + CO_BORROWER = 'CO_BORROWER', + PRIMARY_BORROWER_SPOUSE = 'PRIMARY_BORROWER_SPOUSE', + CO_BORROWER_SPOUSE = 'CO_BORROWER_SPOUSE' +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.resolver.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.resolver.ts new file mode 100644 index 0000000..b615898 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.resolver.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { Resolve, Router } from '@angular/router'; +import { ConfigurationService } from './configuration.service'; +import { UiSettings } from './ui-settings'; +import { Observable, Subject } from 'rxjs'; + +@Injectable() +export class ConfigurationResolver implements Resolve { + + public ERROR_ROUTE = '/error'; + public DOWN_ROUTE = '/down'; + + constructor(private configurationService: ConfigurationService, private router: Router) {} + + public resolve(): Observable | boolean { + if (this.configurationService.hasValidConfiguration()) { + return true; + } + const isDoneResolvingSubject = new Subject(); + this.configurationService.reloadConfiguration() + .subscribe( + (success: UiSettings) => { + this.configurationService.saveConfiguration(success); + if (this.configurationService.isApplicationDisabled()) { + this.router.navigate([this.DOWN_ROUTE]); + isDoneResolvingSubject.next(false); + isDoneResolvingSubject.complete(); + } + isDoneResolvingSubject.next(true); + isDoneResolvingSubject.complete(); + }, + (failure) => { + this.router.navigate([this.ERROR_ROUTE]); + isDoneResolvingSubject.next(false); + isDoneResolvingSubject.complete(); + } + ); + + return isDoneResolvingSubject.asObservable(); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.service.spec.ts new file mode 100644 index 0000000..33e83b0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.service.spec.ts @@ -0,0 +1,92 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { ConfigurationService } from './configuration.service'; +import { UiSettings } from './ui-settings'; +import { DefaultSettings } from './default-settings.mock'; +import { HttpClient } from '@angular/common/http'; +import { configureTestSuite } from 'ng-bullet'; +import { environment } from 'src/environments/environment'; + + +describe('Configuration Service', () => { + + let service: ConfigurationService; + + const defaultSettings: UiSettings = DefaultSettings; + let httpClient: HttpClient; + let httpTestingController: HttpTestingController; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + ConfigurationService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(ConfigurationService); + httpClient = TestBed.get(HttpClient); + httpTestingController = TestBed.get(HttpTestingController); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should save and return configurations', () => { + service.saveConfiguration(defaultSettings); + const links = service.getConfigurationByField('links'); + expect(links.HomePage).toBe('https://www.landmarkcu.com/'); + const allConfig: UiSettings = service.getAllConfiguration(); + expect(allConfig.mortgageRefiExpress[0]).toEqual(96); + }); + + it('should load configuration', () => { + const originalUseLocalSettings = environment.useLocalSettings; + environment.useLocalSettings = false; + + service.reloadConfiguration().subscribe((data) => { + expect(data.test).toEqual('x'); + service.saveConfiguration(data); + } + ); + const req = httpTestingController.expectOne(service.buildPingURL()); + + // Assert that the request is a GET. + expect(req.request.method).toEqual('GET'); + + // Respond with mock data, causing Observable to resolve. + // Subscribe callback asserts that correct data was returned. + req.flush({'test': 'x'}); + + // Finally, assert that there are no outstanding requests. + httpTestingController.verify(); + environment.useLocalSettings = originalUseLocalSettings; + }); + + it('should throw an error if there is no configuration', () => { + let allConfig; + try { + allConfig = service.getAllConfiguration(); + } catch (exception) { + allConfig = exception; + } + expect(allConfig.toString()).toBe('Error: UI Settings not loaded'); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.service.ts new file mode 100644 index 0000000..756eeaa --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.service.ts @@ -0,0 +1,73 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { UiSettings, SiteLink } from './ui-settings'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ConfigurationService { + + public static readonly LOCAL_SETTINGS_URL = `${environment.baseHref}/assets/settings/default_settings.json`; + private uiSettings: UiSettings; + + constructor(private httpClient: HttpClient) {} + + private buildHeaders(): HttpHeaders { + return new HttpHeaders().set('withCredentials', 'true'); + } + + public buildPingURL(): string { + return environment.urlBase + '/UiSettings/'; + } + + public reloadConfiguration(): Observable { + if (this.shouldUseLocalSettings()) { + console.log('using local settings...'); + return this.httpClient.get(ConfigurationService.LOCAL_SETTINGS_URL); + } + return this.httpClient.get(this.buildPingURL(), {headers: this.buildHeaders()}); + } + + public hasValidConfiguration(): boolean { + return !!(this.uiSettings); + } + + public shouldUseLocalSettings() { + return environment.useLocalSettings; + } + + public saveConfiguration(config: UiSettings): void { + this.uiSettings = config; + } + + public getConfigurationByField(field: string) { + if (!this.uiSettings) { + throw new Error('UI Settings not loaded'); + } + return this.uiSettings[field]; + } + + public getSiteLink(site: SiteLink): string { + return this.getConfigurationByField('links')[site]; + } + + public getSystemConfiguration(configuration: string): string { + return this.getConfigurationByField('configuration')[configuration]; + } + + public isApplicationDisabled(): boolean { + if (this.getSystemConfiguration('applicationDisabled')) { + return true; + } + return false; + } + + public getAllConfiguration(): UiSettings { + if (this.uiSettings) { + return this.uiSettings; + } + throw new Error('UI Settings not loaded'); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.ts new file mode 100644 index 0000000..63788d1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/configuration.ts @@ -0,0 +1,6 @@ +export class Configuration { + public id: string; + public lastUpdated: Date; + public lastUpdatedBy: string; + public applicationDisabled: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/default-settings.mock.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/default-settings.mock.ts new file mode 100644 index 0000000..bf9e994 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/default-settings.mock.ts @@ -0,0 +1,1102 @@ +import { BranchConfig, UiSettings, BranchNameConfig } from './ui-settings'; + +export const DefaultSettings: UiSettings = { + 'configuration': { + 'lastUpdated': new Date(), + 'lastUpdatedBy': 'gfrick', + 'applicationDisabled': false, + 'id': '758f64a9-0e3e-41a1-8b09-e3c37b27437b' + }, + 'mfdHomeAndRvByAmount': { + '10000': [ + 84 + ], + '20000': [ + 120, + 180 + ] + }, + 'homeEquity': [ + 60, + 84, + 120, + 180, + 240 + ], + 'mortgageRefiExpress': [ + 96, + 120, + 144 + ], + 'vehiclePreApproval': [ + 36, + 48, + 63, + 75 + ], + 'termsByYear': { + '2010': [ + 36, + 48 + ], + '2011': [ + 36, + 48 + ], + '2012': [ + 36, + 48 + ], + '2013': [ + 36, + 48, + 63 + ], + '2014': [ + 36, + 48, + 63, + 75 + ], + '2015': [ + 36, + 48, + 63, + 75 + ], + '2016': [ + 36, + 48, + 63, + 75 + ], + '2017': [ + 36, + 48, + 63, + 75, + ], + '2018': [ + 36, + 48, + 63, + 75, + ], + '2019': [ + 36, + 48, + 63, + 75, + 84 + ] + }, + 'boatLoansByAmount': { + '0': [ + 63 + ], + '10000': [ + 63, + 84 + ], + '20000': [ + 120, + 180 + ] + }, + 'powersportByAmount': { + '0': [ + 63 + ], + '10000': [ + 72 + ] + }, + 'signatureByAmount': { + '0': [ + 12, + 24, + 36 + ], + '5000': [ + 48 + ] + }, + 'typePurposes': { + 'CC_CashBack': [ + 'Other', + 'Consolidation', + 'Reestablish_Credit' + ], + 'CC_Rewards': [ + 'Other', + 'Consolidation', + 'Reestablish_Credit' + ], + 'CC_Student': [ + 'Other', + 'Consolidation', + 'Reestablish_Credit' + ], + 'CC_Secure': [ + 'Other', + 'Consolidation', + 'Reestablish_Credit' + ], + 'EQ_HELOC_Flex': [ + 'Home_Improvement', + 'Home_Purchase', + 'Home_Refinance', + 'Other', + 'Cash_Out_Refinance' + ], + 'EQ_Mtg_Refi_Express': [ + 'Home_Improvement', + 'Home_Purchase', + 'Home_Refinance', + 'Other', + 'Cash_Out_Refinance' + ], + 'EQ_Home_Equity_Fixed': [ + 'Home_Improvement', + 'Home_Purchase', + 'Home_Refinance', + 'Other', + 'Cash_Out_Refinance' + ], + 'EQ_Mfg_Home_With_or_Without_Land': [ + 'Home_Improvement', + 'Purchase_Manufactured_Home', + 'Home_Refinance', + 'Other', + 'Cash_Out_Refinance' + ], + 'V_Vehicle': [ + 'Purchase', + 'Cash_Out_Refinance', + 'Refinance' + ], + 'V_VehiclePreapproval': [ + 'Purchase', + 'Cash_Out_Refinance', + 'Refinance' + ], + 'M_Boat': [ + 'Purchase', + 'Refinance' + ], + 'P_Powersport': [ + 'Purchase', + 'Refinance' + ], + 'T_TrailerMotorhome': [ + 'Purchase', + 'Refinance' + ], + 'S_Signature': [ + 'Other', + 'Consolidation' + ] + }, + 'limits': { + 'CC_CashBack': { + 'min': 250, + 'max': 25000 + }, + 'CC_Rewards': { + 'min': 250, + 'max': 25000 + }, + 'CC_Student': { + 'min': 250, + 'max': 25000 + }, + 'CC_Secure': { + 'min': 250, + 'max': 25000 + }, + 'EQ_HELOC_Flex': { + 'min': 5000, + 'max': 250000 + }, + 'EQ_Mtg_Refi_Express': { + 'min': 5000, + 'max': 250000 + }, + 'EQ_Home_Equity_Fixed': { + 'min': 5000, + 'max': 250000 + }, + 'EQ_Mfg_Home_With_or_Without_Land': { + 'min': 10000, + 'max': 250000 + }, + 'V_Vehicle': { + 'min': 250, + 'max': 250000 + }, + 'V_VehiclePreapproval': { + 'min': 250, + 'max': 250000 + }, + 'M_Boat': { + 'min': 250, + 'max': 250000 + }, + 'P_Powersport': { + 'min': 250, + 'max': 250000 + }, + 'T_TrailerMotorhome': { + 'min': 10000, + 'max': 250000 + }, + 'S_Signature': { + 'min': 250, + 'max': 25000 + } + }, + 'statesRequiringSpouseInformation': [ + 'WI' + ], + 'stateOptions': [ + 'AA', + 'AE', + 'AK', + 'AL', + 'AP', + 'AR', + 'AS', + 'AZ', + 'CA', + 'CO', + 'CT', + 'DC', + 'DE', + 'FL', + 'GA', + 'GU', + 'HI', + 'IA', + 'ID', + 'IL', + 'IN', + 'KS', + 'KY', + 'LA', + 'MA', + 'MD', + 'ME', + 'MI', + 'MN', + 'MO', + 'MP', + 'MS', + 'MT', + 'NC', + 'ND', + 'NE', + 'NH', + 'NJ', + 'NM', + 'NV', + 'NY', + 'OH', + 'OK', + 'OR', + 'PA', + 'PR', + 'PW', + 'RI', + 'SC', + 'SD', + 'TN', + 'TX', + 'UT', + 'VA', + 'VI', + 'VT', + 'WA', + 'WI', + 'WV', + 'WY' + ], + 'abaRouting': '275079714', + 'contact': { + 'HelpPhone': '262-796-4500' + }, + 'links': { + 'PrivacyPolicy': 'https://www.example.com/', + 'MemberSite': 'https://www.example.net', + 'HomePage': 'https://www.landmarkcu.com/' + }, + 'incomeLimits': { + 'Hourly': { + 'min': 0, + 'max': 999 + }, + 'Annually': { + 'min': 999, + 'max': 1000000000 + } + }, + 'branchMapping': { + 'branchCodesByZip': [ + {'zipcode': '53001', 'channelId': 1561599}, + {'zipcode': '53002', 'channelId': 1561599}, + {'zipcode': '53003', 'channelId': 1561245}, + {'zipcode': '53004', 'channelId': 1561591}, + {'zipcode': '53005', 'channelId': 1561255}, + {'zipcode': '53006', 'channelId': 1561657}, + {'zipcode': '53007', 'channelId': 1561255}, + {'zipcode': '53008', 'channelId': 1561255}, + {'zipcode': '53010', 'channelId': 1561599}, + {'zipcode': '53011', 'channelId': 1561599}, + {'zipcode': '53012', 'channelId': 1561591}, + {'zipcode': '53013', 'channelId': 1561591}, + {'zipcode': '53014', 'channelId': 1561657}, + {'zipcode': '53015', 'channelId': 1561241}, + {'zipcode': '53016', 'channelId': 1561665}, + {'zipcode': '53017', 'channelId': 1561664}, + {'zipcode': '53018', 'channelId': 1561267}, + {'zipcode': '53019', 'channelId': 1561599}, + {'zipcode': '53020', 'channelId': 1561599}, + {'zipcode': '53021', 'channelId': 1561604}, + {'zipcode': '53022', 'channelId': 1561288}, + {'zipcode': '53023', 'channelId': 1561288}, + {'zipcode': '53024', 'channelId': 1561591}, + {'zipcode': '53026', 'channelId': 1561591}, + {'zipcode': '53027', 'channelId': 1561662}, + {'zipcode': '53029', 'channelId': 1561267}, + {'zipcode': '53031', 'channelId': 1561599}, + {'zipcode': '53032', 'channelId': 1561657}, + {'zipcode': '53033', 'channelId': 1561664}, + {'zipcode': '53034', 'channelId': 1561665}, + {'zipcode': '53035', 'channelId': 1561657}, + {'zipcode': '53036', 'channelId': 1561284}, + {'zipcode': '53037', 'channelId': 1561664}, + {'zipcode': '53038', 'channelId': 1561284}, + {'zipcode': '53039', 'channelId': 1561665}, + {'zipcode': '53040', 'channelId': 1561599}, + {'zipcode': '53042', 'channelId': 1561599}, + {'zipcode': '53044', 'channelId': 1561599}, + {'zipcode': '53045', 'channelId': 1561588}, + {'zipcode': '53046', 'channelId': 1561265}, + {'zipcode': '53047', 'channelId': 1561284}, + {'zipcode': '53048', 'channelId': 1561662}, + {'zipcode': '53049', 'channelId': 1561657}, + {'zipcode': '53050', 'channelId': 1561657}, + {'zipcode': '53051', 'channelId': 1561288}, + {'zipcode': '53052', 'channelId': 1561288}, + {'zipcode': '53056', 'channelId': 1561267}, + {'zipcode': '53057', 'channelId': 1561599}, + {'zipcode': '53058', 'channelId': 1561267}, + {'zipcode': '53059', 'channelId': 1561662}, + {'zipcode': '53060', 'channelId': 1561599}, + {'zipcode': '53061', 'channelId': 1561599}, + {'zipcode': '53062', 'channelId': 1561599}, + {'zipcode': '53063', 'channelId': 1561599}, + {'zipcode': '53064', 'channelId': 1561267}, + {'zipcode': '53065', 'channelId': 1561657}, + {'zipcode': '53066', 'channelId': 1561245}, + {'zipcode': '53069', 'channelId': 1561267}, + {'zipcode': '53070', 'channelId': 1561591}, + {'zipcode': '53072', 'channelId': 1561262}, + {'zipcode': '53073', 'channelId': 1561599}, + {'zipcode': '53074', 'channelId': 1561591}, + {'zipcode': '53075', 'channelId': 1561599}, + {'zipcode': '53076', 'channelId': 1561664}, + {'zipcode': '53078', 'channelId': 1561662}, + {'zipcode': '53079', 'channelId': 1561599}, + {'zipcode': '53080', 'channelId': 1561591}, + {'zipcode': '53081', 'channelId': 1561591}, + {'zipcode': '53082', 'channelId': 1561604}, + {'zipcode': '53083', 'channelId': 1561599}, + {'zipcode': '53085', 'channelId': 1561599}, + {'zipcode': '53086', 'channelId': 1561662}, + {'zipcode': '53088', 'channelId': 1561657}, + {'zipcode': '53089', 'channelId': 1561265}, + {'zipcode': '53090', 'channelId': 1561599}, + {'zipcode': '53091', 'channelId': 1561657}, + {'zipcode': '53092', 'channelId': 1561591}, + {'zipcode': '53093', 'channelId': 1561599}, + {'zipcode': '53094', 'channelId': 1561284}, + {'zipcode': '53095', 'channelId': 1561604}, + {'zipcode': '53096', 'channelId': 1561599}, + {'zipcode': '53097', 'channelId': 1561591}, + {'zipcode': '53098', 'channelId': 1561284}, + {'zipcode': '53099', 'channelId': 1561662}, + {'zipcode': '53101', 'channelId': 1561631}, + {'zipcode': '53102', 'channelId': 1561631}, + {'zipcode': '53103', 'channelId': 2081492}, + {'zipcode': '53104', 'channelId': 1561624}, + {'zipcode': '53105', 'channelId': 1561631}, + {'zipcode': '53108', 'channelId': 1561598}, + {'zipcode': '53109', 'channelId': 1561631}, + {'zipcode': '53110', 'channelId': 1561616}, + {'zipcode': '53114', 'channelId': 1561631}, + {'zipcode': '53115', 'channelId': 1561631}, + {'zipcode': '53118', 'channelId': 1561245}, + {'zipcode': '53119', 'channelId': 1561245}, + {'zipcode': '53120', 'channelId': 1561631}, + {'zipcode': '53121', 'channelId': 1561631}, + {'zipcode': '53122', 'channelId': 1561588}, + {'zipcode': '53125', 'channelId': 1561631}, + {'zipcode': '53126', 'channelId': 1561624}, + {'zipcode': '53127', 'channelId': 1561262}, + {'zipcode': '53128', 'channelId': 1561631}, + {'zipcode': '53129', 'channelId': 1561289}, + {'zipcode': '53130', 'channelId': 1561289}, + {'zipcode': '53132', 'channelId': 1561289}, + {'zipcode': '53137', 'channelId': 1561284}, + {'zipcode': '53138', 'channelId': 1561661}, + {'zipcode': '53139', 'channelId': 1561631}, + {'zipcode': '53140', 'channelId': 1561639}, + {'zipcode': '53141', 'channelId': 1561639}, + {'zipcode': '53142', 'channelId': 1561639}, + {'zipcode': '53143', 'channelId': 1561639}, + {'zipcode': '53144', 'channelId': 1561639}, + {'zipcode': '53146', 'channelId': 1561243}, + {'zipcode': '53147', 'channelId': 1561631}, + {'zipcode': '53148', 'channelId': 1561631}, + {'zipcode': '53149', 'channelId': 2081492}, + {'zipcode': '53150', 'channelId': 2081492}, + {'zipcode': '53151', 'channelId': 1561243}, + {'zipcode': '53152', 'channelId': 1561631}, + {'zipcode': '53153', 'channelId': 1561262}, + {'zipcode': '53154', 'channelId': 1561598}, + {'zipcode': '53156', 'channelId': 1561245}, + {'zipcode': '53157', 'channelId': 1561631}, + {'zipcode': '53158', 'channelId': 1561639}, + {'zipcode': '53159', 'channelId': 1561631}, + {'zipcode': '53167', 'channelId': 1561631}, + {'zipcode': '53168', 'channelId': 1561631}, + {'zipcode': '53170', 'channelId': 1561631}, + {'zipcode': '53171', 'channelId': 1561639}, + {'zipcode': '53172', 'channelId': 1561598}, + {'zipcode': '53176', 'channelId': 1561631}, + {'zipcode': '53177', 'channelId': 1561624}, + {'zipcode': '53178', 'channelId': 1561245}, + {'zipcode': '53179', 'channelId': 1561631}, + {'zipcode': '53181', 'channelId': 1561631}, + {'zipcode': '53182', 'channelId': 1561624}, + {'zipcode': '53183', 'channelId': 1561267}, + {'zipcode': '53184', 'channelId': 1561631}, + {'zipcode': '53185', 'channelId': 1561631}, + {'zipcode': '53186', 'channelId': 1561262}, + {'zipcode': '53187', 'channelId': 1561262}, + {'zipcode': '53188', 'channelId': 1561262}, + {'zipcode': '53189', 'channelId': 1561262}, + {'zipcode': '53190', 'channelId': 1561245}, + {'zipcode': '53191', 'channelId': 1561631}, + {'zipcode': '53192', 'channelId': 1561631}, + {'zipcode': '53194', 'channelId': 1561624}, + {'zipcode': '53195', 'channelId': 1561631}, + {'zipcode': '53199', 'channelId': 1561631}, + {'zipcode': '53201', 'channelId': 1561241}, + {'zipcode': '53202', 'channelId': 1561615}, + {'zipcode': '53203', 'channelId': 1561241}, + {'zipcode': '53204', 'channelId': 1561241}, + {'zipcode': '53205', 'channelId': 1561241}, + {'zipcode': '53206', 'channelId': 1561241}, + {'zipcode': '53207', 'channelId': 1561615}, + {'zipcode': '53208', 'channelId': 1561241}, + {'zipcode': '53209', 'channelId': 1561255}, + {'zipcode': '53210', 'channelId': 1561241}, + {'zipcode': '53211', 'channelId': 1561615}, + {'zipcode': '53212', 'channelId': 1561241}, + {'zipcode': '53213', 'channelId': 1561241}, + {'zipcode': '53214', 'channelId': 1561587}, + {'zipcode': '53215', 'channelId': 1561283}, + {'zipcode': '53216', 'channelId': 1561241}, + {'zipcode': '53217', 'channelId': 1561255}, + {'zipcode': '53218', 'channelId': 1561255}, + {'zipcode': '53219', 'channelId': 1561241}, + {'zipcode': '53220', 'channelId': 1561283}, + {'zipcode': '53221', 'channelId': 1561283}, + {'zipcode': '53222', 'channelId': 1561255}, + {'zipcode': '53223', 'channelId': 1561255}, + {'zipcode': '53224', 'channelId': 1561255}, + {'zipcode': '53225', 'channelId': 1561255}, + {'zipcode': '53226', 'channelId': 1561255}, + {'zipcode': '53227', 'channelId': 1561587}, + {'zipcode': '53228', 'channelId': 1561587}, + {'zipcode': '53233', 'channelId': 1561241}, + {'zipcode': '53234', 'channelId': 1561241}, + {'zipcode': '53235', 'channelId': 1561616}, + {'zipcode': '53237', 'channelId': 1561283}, + {'zipcode': '53259', 'channelId': 1561615}, + {'zipcode': '53263', 'channelId': 1561255}, + {'zipcode': '53267', 'channelId': 1561241}, + {'zipcode': '53268', 'channelId': 1561615}, + {'zipcode': '53274', 'channelId': 1561283}, + {'zipcode': '53278', 'channelId': 1561241}, + {'zipcode': '53288', 'channelId': 1561615}, + {'zipcode': '53290', 'channelId': 1561241}, + {'zipcode': '53293', 'channelId': 1561241}, + {'zipcode': '53295', 'channelId': 1561241}, + {'zipcode': '53401', 'channelId': 1561639}, + {'zipcode': '53402', 'channelId': 1561285}, + {'zipcode': '53403', 'channelId': 1561639}, + {'zipcode': '53404', 'channelId': 1561639}, + {'zipcode': '53405', 'channelId': 1561285}, + {'zipcode': '53406', 'channelId': 1561624}, + {'zipcode': '53407', 'channelId': 1561639}, + {'zipcode': '53408', 'channelId': 1561624}, + {'zipcode': '53490', 'channelId': 1561285}, + {'zipcode': '53501', 'channelId': 1561661}, + {'zipcode': '53502', 'channelId': 1561661}, + {'zipcode': '53503', 'channelId': 1561661}, + {'zipcode': '53505', 'channelId': 1561631}, + {'zipcode': '53506', 'channelId': 1561661}, + {'zipcode': '53507', 'channelId': 1561661}, + {'zipcode': '53508', 'channelId': 1561661}, + {'zipcode': '53511', 'channelId': 1561661}, + {'zipcode': '53512', 'channelId': 1561661}, + {'zipcode': '53515', 'channelId': 1561661}, + {'zipcode': '53517', 'channelId': 1561661}, + {'zipcode': '53520', 'channelId': 1561661}, + {'zipcode': '53521', 'channelId': 1561661}, + {'zipcode': '53522', 'channelId': 1561661}, + {'zipcode': '53523', 'channelId': 1561284}, + {'zipcode': '53525', 'channelId': 1561661}, + {'zipcode': '53526', 'channelId': 1561661}, + {'zipcode': '53527', 'channelId': 1561661}, + {'zipcode': '53528', 'channelId': 1561661}, + {'zipcode': '53529', 'channelId': 1561661}, + {'zipcode': '53531', 'channelId': 1561661}, + {'zipcode': '53532', 'channelId': 1561661}, + {'zipcode': '53533', 'channelId': 1561661}, + {'zipcode': '53534', 'channelId': 1561661}, + {'zipcode': '53535', 'channelId': 1561661}, + {'zipcode': '53536', 'channelId': 1561661}, + {'zipcode': '53537', 'channelId': 1561661}, + {'zipcode': '53538', 'channelId': 1561284}, + {'zipcode': '53542', 'channelId': 1561661}, + {'zipcode': '53543', 'channelId': 1561661}, + {'zipcode': '53544', 'channelId': 1561661}, + {'zipcode': '53545', 'channelId': 1561661}, + {'zipcode': '53546', 'channelId': 1561631}, + {'zipcode': '53547', 'channelId': 1561661}, + {'zipcode': '53548', 'channelId': 1561661}, + {'zipcode': '53549', 'channelId': 1561284}, + {'zipcode': '53550', 'channelId': 1561661}, + {'zipcode': '53551', 'channelId': 1561284}, + {'zipcode': '53553', 'channelId': 1561661}, + {'zipcode': '53555', 'channelId': 1561661}, + {'zipcode': '53557', 'channelId': 1561665}, + {'zipcode': '53558', 'channelId': 1561661}, + {'zipcode': '53559', 'channelId': 1561660}, + {'zipcode': '53560', 'channelId': 1561661}, + {'zipcode': '53562', 'channelId': 1561661}, + {'zipcode': '53563', 'channelId': 1561284}, + {'zipcode': '53565', 'channelId': 1561661}, + {'zipcode': '53566', 'channelId': 1561661}, + {'zipcode': '53570', 'channelId': 1561661}, + {'zipcode': '53571', 'channelId': 1561661}, + {'zipcode': '53572', 'channelId': 1561661}, + {'zipcode': '53574', 'channelId': 1561661}, + {'zipcode': '53575', 'channelId': 1561661}, + {'zipcode': '53576', 'channelId': 1561661}, + {'zipcode': '53579', 'channelId': 1561660}, + {'zipcode': '53580', 'channelId': 1561661}, + {'zipcode': '53582', 'channelId': 1561661}, + {'zipcode': '53585', 'channelId': 1561631}, + {'zipcode': '53589', 'channelId': 1561661}, + {'zipcode': '53590', 'channelId': 1561661}, + {'zipcode': '53593', 'channelId': 1561661}, + {'zipcode': '53594', 'channelId': 1561284}, + {'zipcode': '53595', 'channelId': 1561661}, + {'zipcode': '53596', 'channelId': 1561661}, + {'zipcode': '53597', 'channelId': 1561661}, + {'zipcode': '53598', 'channelId': 1561661}, + {'zipcode': '53701', 'channelId': 1561661}, + {'zipcode': '53702', 'channelId': 1561661}, + {'zipcode': '53703', 'channelId': 1561661}, + {'zipcode': '53704', 'channelId': 1561661}, + {'zipcode': '53705', 'channelId': 1561661}, + {'zipcode': '53706', 'channelId': 1561661}, + {'zipcode': '53707', 'channelId': 1561661}, + {'zipcode': '53708', 'channelId': 1561661}, + {'zipcode': '53711', 'channelId': 1561661}, + {'zipcode': '53713', 'channelId': 1561661}, + {'zipcode': '53714', 'channelId': 1561661}, + {'zipcode': '53715', 'channelId': 1561661}, + {'zipcode': '53716', 'channelId': 1561661}, + {'zipcode': '53717', 'channelId': 1561661}, + {'zipcode': '53718', 'channelId': 1561661}, + {'zipcode': '53719', 'channelId': 1561661}, + {'zipcode': '53725', 'channelId': 1561661}, + {'zipcode': '53726', 'channelId': 1561661}, + {'zipcode': '53744', 'channelId': 1561661}, + {'zipcode': '53774', 'channelId': 1561661}, + {'zipcode': '53777', 'channelId': 1561661}, + {'zipcode': '53778', 'channelId': 1561661}, + {'zipcode': '53779', 'channelId': 1561661}, + {'zipcode': '53782', 'channelId': 1561661}, + {'zipcode': '53783', 'channelId': 1561661}, + {'zipcode': '53784', 'channelId': 1561661}, + {'zipcode': '53785', 'channelId': 1561661}, + {'zipcode': '53786', 'channelId': 1561661}, + {'zipcode': '53788', 'channelId': 1561661}, + {'zipcode': '53789', 'channelId': 1561661}, + {'zipcode': '53790', 'channelId': 1561661}, + {'zipcode': '53791', 'channelId': 1561661}, + {'zipcode': '53792', 'channelId': 1561661}, + {'zipcode': '53793', 'channelId': 1561661}, + {'zipcode': '53794', 'channelId': 1561661}, + {'zipcode': '53901', 'channelId': 1561660}, + {'zipcode': '53911', 'channelId': 1561661}, + {'zipcode': '53916', 'channelId': 1561649}, + {'zipcode': '53919', 'channelId': 1561649}, + {'zipcode': '53920', 'channelId': 1561660}, + {'zipcode': '53922', 'channelId': 1561657}, + {'zipcode': '53923', 'channelId': 1561660}, + {'zipcode': '53925', 'channelId': 1561660}, + {'zipcode': '53926', 'channelId': 1561660}, + {'zipcode': '53928', 'channelId': 1561660}, + {'zipcode': '53930', 'channelId': 1561660}, + {'zipcode': '53931', 'channelId': 1561649}, + {'zipcode': '53932', 'channelId': 1561660}, + {'zipcode': '53933', 'channelId': 1561649}, + {'zipcode': '53935', 'channelId': 1561660}, + {'zipcode': '53939', 'channelId': 1561649}, + {'zipcode': '53946', 'channelId': 1561649}, + {'zipcode': '53947', 'channelId': 1561649}, + {'zipcode': '53949', 'channelId': 1561660}, + {'zipcode': '53952', 'channelId': 1561660}, + {'zipcode': '53953', 'channelId': 1561660}, + {'zipcode': '53954', 'channelId': 1561660}, + {'zipcode': '53955', 'channelId': 1561660}, + {'zipcode': '53956', 'channelId': 1561660}, + {'zipcode': '53957', 'channelId': 1561649}, + {'zipcode': '53960', 'channelId': 1561660}, + {'zipcode': '53963', 'channelId': 1561657}, + {'zipcode': '53964', 'channelId': 1561660}, + {'zipcode': '53965', 'channelId': 1561660}, + {'zipcode': '53969', 'channelId': 1561661}, + {'zipcode': '54106', 'channelId': 1561657}, + {'zipcode': '54110', 'channelId': 1561599}, + {'zipcode': '54113', 'channelId': 1561657}, + {'zipcode': '54115', 'channelId': 1561657}, + {'zipcode': '54123', 'channelId': 1561657}, + {'zipcode': '54126', 'channelId': 1561599}, + {'zipcode': '54129', 'channelId': 1561657}, + {'zipcode': '54130', 'channelId': 1561657}, + {'zipcode': '54131', 'channelId': 1561657}, + {'zipcode': '54136', 'channelId': 1561657}, + {'zipcode': '54140', 'channelId': 1561657}, + {'zipcode': '54152', 'channelId': 1561657}, + {'zipcode': '54155', 'channelId': 1561657}, + {'zipcode': '54160', 'channelId': 1561599}, + {'zipcode': '54162', 'channelId': 1561657}, + {'zipcode': '54165', 'channelId': 1561657}, + {'zipcode': '54169', 'channelId': 1561657}, + {'zipcode': '54170', 'channelId': 1561657}, + {'zipcode': '54173', 'channelId': 1561657}, + {'zipcode': '54180', 'channelId': 1561657}, + {'zipcode': '54207', 'channelId': 1561599}, + {'zipcode': '54208', 'channelId': 1561599}, + {'zipcode': '54214', 'channelId': 1561599}, + {'zipcode': '54215', 'channelId': 1561599}, + {'zipcode': '54220', 'channelId': 1561599}, + {'zipcode': '54221', 'channelId': 1561599}, + {'zipcode': '54227', 'channelId': 1561599}, + {'zipcode': '54228', 'channelId': 1561599}, + {'zipcode': '54229', 'channelId': 1561599}, + {'zipcode': '54230', 'channelId': 1561599}, + {'zipcode': '54232', 'channelId': 1561599}, + {'zipcode': '54240', 'channelId': 1561599}, + {'zipcode': '54241', 'channelId': 1561599}, + {'zipcode': '54245', 'channelId': 1561599}, + {'zipcode': '54247', 'channelId': 1561599}, + {'zipcode': '54301', 'channelId': 1561657}, + {'zipcode': '54302', 'channelId': 1561599}, + {'zipcode': '54303', 'channelId': 1561657}, + {'zipcode': '54304', 'channelId': 1561657}, + {'zipcode': '54305', 'channelId': 1561657}, + {'zipcode': '54306', 'channelId': 1561657}, + {'zipcode': '54307', 'channelId': 1561657}, + {'zipcode': '54308', 'channelId': 1561657}, + {'zipcode': '54311', 'channelId': 1561599}, + {'zipcode': '54313', 'channelId': 1561657}, + {'zipcode': '54324', 'channelId': 1561657}, + {'zipcode': '54344', 'channelId': 1561657}, + {'zipcode': '54901', 'channelId': 1561657}, + {'zipcode': '54902', 'channelId': 1561657}, + {'zipcode': '54903', 'channelId': 1561657}, + {'zipcode': '54904', 'channelId': 1561657}, + {'zipcode': '54906', 'channelId': 1561657}, + {'zipcode': '54911', 'channelId': 1561657}, + {'zipcode': '54912', 'channelId': 1561657}, + {'zipcode': '54913', 'channelId': 1561657}, + {'zipcode': '54914', 'channelId': 1561657}, + {'zipcode': '54915', 'channelId': 1561657}, + {'zipcode': '54919', 'channelId': 1561657}, + {'zipcode': '54922', 'channelId': 1561657}, + {'zipcode': '54923', 'channelId': 1561615}, + {'zipcode': '54927', 'channelId': 1561657}, + {'zipcode': '54931', 'channelId': 1561657}, + {'zipcode': '54932', 'channelId': 1561657}, + {'zipcode': '54934', 'channelId': 1561649}, + {'zipcode': '54935', 'channelId': 1561657}, + {'zipcode': '54936', 'channelId': 1561657}, + {'zipcode': '54937', 'channelId': 1561657}, + {'zipcode': '54941', 'channelId': 1561649}, + {'zipcode': '54942', 'channelId': 1561657}, + {'zipcode': '54944', 'channelId': 1561657}, + {'zipcode': '54947', 'channelId': 1561657}, + {'zipcode': '54952', 'channelId': 1561657}, + {'zipcode': '54956', 'channelId': 1561657}, + {'zipcode': '54957', 'channelId': 1561657}, + {'zipcode': '54960', 'channelId': 1561649}, + {'zipcode': '54963', 'channelId': 1561657}, + {'zipcode': '54964', 'channelId': 1561657}, + {'zipcode': '54968', 'channelId': 1561649}, + {'zipcode': '54971', 'channelId': 1561649}, + {'zipcode': '54974', 'channelId': 1561657}, + {'zipcode': '54979', 'channelId': 1561657}, + {'zipcode': '54980', 'channelId': 1561649}, + {'zipcode': '54985', 'channelId': 1561657}, + {'zipcode': '54986', 'channelId': 1561657}, + {'zipcode': '60001', 'channelId': 1561631}, + {'zipcode': '60002', 'channelId': 1561624}, + {'zipcode': '60010', 'channelId': 1561631}, + {'zipcode': '60011', 'channelId': 1561631}, + {'zipcode': '60012', 'channelId': 1561631}, + {'zipcode': '60013', 'channelId': 1561631}, + {'zipcode': '60014', 'channelId': 1561631}, + {'zipcode': '60015', 'channelId': 1561639}, + {'zipcode': '60020', 'channelId': 1561631}, + {'zipcode': '60021', 'channelId': 1561631}, + {'zipcode': '60030', 'channelId': 1561631}, + {'zipcode': '60031', 'channelId': 1561639}, + {'zipcode': '60033', 'channelId': 1561631}, + {'zipcode': '60034', 'channelId': 1561631}, + {'zipcode': '60035', 'channelId': 1561639}, + {'zipcode': '60037', 'channelId': 1561639}, + {'zipcode': '60039', 'channelId': 1561631}, + {'zipcode': '60040', 'channelId': 1561639}, + {'zipcode': '60041', 'channelId': 1561631}, + {'zipcode': '60042', 'channelId': 1561631}, + {'zipcode': '60044', 'channelId': 1561639}, + {'zipcode': '60045', 'channelId': 1561639}, + {'zipcode': '60046', 'channelId': 1561631}, + {'zipcode': '60047', 'channelId': 1561631}, + {'zipcode': '60048', 'channelId': 1561639}, + {'zipcode': '60049', 'channelId': 1561631}, + {'zipcode': '60050', 'channelId': 1561631}, + {'zipcode': '60051', 'channelId': 1561631}, + {'zipcode': '60060', 'channelId': 1561639}, + {'zipcode': '60061', 'channelId': 1561639}, + {'zipcode': '60064', 'channelId': 1561639}, + {'zipcode': '60069', 'channelId': 1561639}, + {'zipcode': '60071', 'channelId': 1561631}, + {'zipcode': '60072', 'channelId': 1561631}, + {'zipcode': '60073', 'channelId': 1561631}, + {'zipcode': '60075', 'channelId': 1561639}, + {'zipcode': '60079', 'channelId': 1561639}, + {'zipcode': '60081', 'channelId': 1561631}, + {'zipcode': '60083', 'channelId': 1561639}, + {'zipcode': '60084', 'channelId': 1561631}, + {'zipcode': '60085', 'channelId': 1561639}, + {'zipcode': '60086', 'channelId': 1561631}, + {'zipcode': '60087', 'channelId': 1561639}, + {'zipcode': '60088', 'channelId': 1561639}, + {'zipcode': '60089', 'channelId': 1561639}, + {'zipcode': '60092', 'channelId': 1561631}, + {'zipcode': '60096', 'channelId': 1561639}, + {'zipcode': '60097', 'channelId': 1561631}, + {'zipcode': '60098', 'channelId': 1561631}, + {'zipcode': '60099', 'channelId': 1561639}, + {'zipcode': '60102', 'channelId': 1561631}, + {'zipcode': '60142', 'channelId': 1561631}, + {'zipcode': '60152', 'channelId': 1561631}, + {'zipcode': '60156', 'channelId': 1561631}, + {'zipcode': '60180', 'channelId': 1561631} + ] + }, + 'branchNameMapping': { + 'branchNamesByChannelId': [ + { + "channelId": 0, + "branchName": "Not Found" + }, + { + "channelId": 1, + "branchName": "ALL" + }, + { + 'channelId': 12801, + 'branchName': 'INDIRECT LENDING DEPT' + }, + { + 'channelId': 1561241, + 'branchName': 'West Milwaukee' + }, + { + 'channelId': 1561243, + 'branchName': 'New Berlin' + }, + { + 'channelId': 1561245, + 'branchName': 'Oconomowoc' + }, + { + 'channelId': 1561255, + 'branchName': 'Wauwatosa' + }, + { + 'channelId': 1561262, + 'branchName': 'Waukesha' + }, + { + 'channelId': 1561265, + 'branchName': 'Sussex' + }, + { + 'channelId': 1561267, + 'branchName': 'Hartland' + }, + { + 'channelId': 1561283, + 'branchName': 'Milwaukee South' + }, + { + 'channelId': 1561284, + 'branchName': 'Watertown' + }, + { + 'channelId': 1561285, + 'branchName': 'Racine Rapids Drive' + }, + { + 'channelId': 1561288, + 'branchName': 'Germantown' + }, + { + 'channelId': 1561289, + 'branchName': 'Franklin' + }, + { + 'channelId': 1561587, + 'branchName': 'West Allis' + }, + { + 'channelId': 1561588, + 'branchName': 'Brookfield East' + }, + { + 'channelId': 1561591, + 'branchName': 'Grafton' + }, + { + 'channelId': 1561598, + 'branchName': 'Oak Creek' + }, + { + 'channelId': 1561599, + 'branchName': 'West Bend-Washington St' + }, + { + 'channelId': 1561604, + 'branchName': 'West Bend-Main St' + }, + { + 'channelId': 1561615, + 'branchName': 'Bay View' + }, + { + 'channelId': 1561616, + 'branchName': 'Cudahy' + }, + { + 'channelId': 1561624, + 'branchName': 'Racine Washington Ave' + }, + { + 'channelId': 1561631, + 'branchName': 'Burlington' + }, + { + 'channelId': 1561639, + 'branchName': 'Racine Grove Ave' + }, + { + 'channelId': 1561649, + 'branchName': 'Beaver Dam' + }, + { + 'channelId': 1561656, + 'branchName': 'Juneau Western Ave' + }, + { + 'channelId': 1561657, + 'branchName': 'Mayville' + }, + { + 'channelId': 1561660, + 'branchName': 'Fall River' + }, + { + 'channelId': 1561661, + 'branchName': 'Madison Spring Street' + }, + { + 'channelId': 1561662, + 'branchName': 'Hartford' + }, + { + 'channelId': 1561664, + 'branchName': 'Hubertus' + }, + { + 'channelId': 1561665, + 'branchName': 'Juneau Main St' + }, + { + 'channelId': 1627725, + 'branchName': 'E Channel' + }, + { + 'channelId': 1698432, + 'branchName': 'Member Service Center' + }, + { + 'channelId': 2081486, + 'branchName': 'ACTion Online' + }, + { + 'channelId': 2081492, + 'branchName': 'Muskego' + }, + { + 'channelId': 2399724, + 'branchName': 'Online Applications' + }, + { + 'channelId': 2399734, + 'branchName': 'Online Credit Card Applications' + }, + { + 'channelId': 2863435, + 'branchName': 'Brookfield West' + }, + { + 'channelId': 2863440, + 'branchName': 'Greenfield' + }, + { + 'channelId': 2863439, + 'branchName': 'Glendale' + } + ] + }, + 'termOptions': [ + { + 'value': 12, + 'desc': '12 months / 1 year' + }, + { + 'value': 24, + 'desc': '24 months / 2 years' + }, + { + 'value': 36, + 'desc': '36 months / 3 years' + }, + { + 'value': 48, + 'desc': '48 months / 4 years' + }, + { + 'value': 60, + 'desc': '60 months / 5 years' + }, + { + 'value': 63, + 'desc': '63 months' + }, + { + 'value': 72, + 'desc': '72 months / 6 years' + }, + { + 'value': 75, + 'desc': '75 months' + }, + { + 'value': 84, + 'desc': '84 months / 7 years' + }, + { + 'value': 120, + 'desc': '120 months / 10 years' + }, + { + 'value': 144, + 'desc': '144 months / 12 years' + }, + { + 'value': 180, + 'desc': '180 months / 15 years' + }, + { + 'value': 240, + 'desc': '240 months / 20 years' + } + ], + 'termOptionsNoYear': [ + { + 'value': 12, + 'desc': '12 months' + }, + { + 'value': 24, + 'desc': '24 months' + }, + { + 'value': 36, + 'desc': '36 months' + }, + { + 'value': 48, + 'desc': '48 months' + }, + { + 'value': 60, + 'desc': '60 months' + }, + { + 'value': 63, + 'desc': '63 months' + }, + { + 'value': 72, + 'desc': '72 months' + }, + { + 'value': 75, + 'desc': '75 months' + }, + { + 'value': 84, + 'desc': '84 months' + }, + { + 'value': 120, + 'desc': '120 months' + }, + { + 'value': 144, + 'desc': '144 months' + }, + { + 'value': 180, + 'desc': '180 months' + }, + { + 'value': 240, + 'desc': '240 months' + } + ] +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/ui-settings.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/ui-settings.ts new file mode 100644 index 0000000..14844d2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/configuration/ui-settings.ts @@ -0,0 +1,117 @@ +import { MinMaxLimit } from '../loan-limit/loan-limit'; +import { Configuration } from './configuration'; + +export class UiSettings { + public homeEquity: Array; + public mortgageRefiExpress: Array; + public mfdHomeAndRvByAmount: any; + public vehiclePreApproval: Array; + public termsByYear: TermsByYear; + public boatLoansByAmount: any; + public powersportByAmount: any; + public signatureByAmount: any; + public statesRequiringSpouseInformation: Array; + public typePurposes: TypePurposes; + public abaRouting: string; + public contact: Contacts; + public links: Links; + public stateOptions: Array; + public limits: LoanLimits; + public incomeLimits: IncomeLimits; + public branchMapping: BranchConfig; + public branchNameMapping: BranchNameConfig; + public termOptions: Array; + public termOptionsNoYear: Array; + public configuration: Configuration; +} + +export interface BranchData { + zipcode: string; + channelId: number; +} + +export interface BranchConfig { + branchCodesByZip: Array; +} + +export interface BranchNameData { + channelId: number; + branchName: string; +} + +export interface BranchNameConfig { + branchNamesByChannelId: Array; +} + +export class TermOption { + public value: any; + public desc: string; +} + +export class IncomeLimits { + public Hourly: MinMaxLimit; + public Annually: MinMaxLimit; +} + +export class LoanLimits { + public CC_CashBack: MinMaxLimit; + public CC_Rewards: MinMaxLimit; + public CC_Student: MinMaxLimit; + public CC_Secure: MinMaxLimit; + public EQ_HELOC_Flex: MinMaxLimit; + public EQ_Mtg_Refi_Express: MinMaxLimit; + public EQ_Home_Equity_Fixed: MinMaxLimit; + public EQ_Mfg_Home_With_or_Without_Land: MinMaxLimit; + public V_Vehicle: MinMaxLimit; + public V_VehiclePreapproval: MinMaxLimit; + public M_Boat: MinMaxLimit; + public P_Powersport: MinMaxLimit; + public T_TrailerMotorhome: MinMaxLimit; + public S_Signature: MinMaxLimit; +} + +class TermsByYear { + public 2010: Array; + public 2011: Array; + public 2012: Array; + public 2013: Array; + public 2014: Array; + public 2015: Array; + public 2016: Array; + public 2017: Array; + public 2018: Array; + public 2019: Array; +} + +class TypePurposes { + public CC_CashBack: Array; + public CC_Rewards: Array; + public CC_Student: Array; + public CC_Secure: Array; + public EQ_HELOC_Flex: Array; + public EQ_Mtg_Refi_Express: Array; + public EQ_Home_Equity_Fixed: Array; + public EQ_Mfg_Home_With_or_Without_Land: Array; + public V_Vehicle: Array; + public V_VehiclePreapproval: Array; + public M_Boat: Array; + public P_Powersport: Array; + public T_TrailerMotorhome: Array; + public S_Signature: Array; +} + +class Contacts { + public HelpPhone: string; +} + +class Links { + public PrivacyPolicy: string; + public MemberSite: string; + public HomePage: string; +} + +export enum SiteLink { + HOME_PAGE = 'HomePage', + MEMBER_SITE = 'MemberSite', + PRIVACY_POLICY = 'PrivacyPolicy' +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/cookie/cookie-util.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/cookie/cookie-util.spec.ts new file mode 100644 index 0000000..4c9e251 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/cookie/cookie-util.spec.ts @@ -0,0 +1,33 @@ +import { CookieUtil } from './cookie-util'; + +describe('CookieUtil', () => { + + afterEach(() => { + document.cookie = 'TokenAuthorization=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + }); + + it('should not find an auth cookie by default', () => { + const hasCookie = CookieUtil.hasAuthCookie(); + expect(hasCookie).toBe(false); + }); + + it('should find an auth cookie when set', () => { + document.cookie = 'TokenAuthorization=112341231234'; + const hasCookie = CookieUtil.hasAuthCookie(); + expect(hasCookie).toBe(true); + }); + + it('should read value of cookie', () => { + document.cookie = 'TokenAuthorization=112341231234'; + const cookieValue = CookieUtil.getCookieValue('TokenAuthorization'); + expect(cookieValue).toBe('112341231234'); + }); + + it('should not read value of cookie when cookie doesnt exist', () => { + document.cookie = 'TokenAuthorization=112341231234'; + const cookieValue = CookieUtil.getCookieValue('RandomCookie'); + expect(cookieValue).toBe(''); + }); +}); + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/cookie/cookie-util.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/cookie/cookie-util.ts new file mode 100644 index 0000000..01fb2f7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/cookie/cookie-util.ts @@ -0,0 +1,61 @@ +import { MemberMeta } from '../../member/models/member-meta'; + +export class CookieUtil { + + public static readonly PUBLIC_COOKIE_KEY = 'TokenAuthorization'; + + public static hasAuthCookie(): boolean { + return document.cookie.indexOf(this.PUBLIC_COOKIE_KEY) > -1; + } + + public static getCookieValue(cookieName: string): string { + const name = cookieName + '='; + const decodedCookie = decodeURIComponent(document.cookie); + const cookieArray = decodedCookie.split(';'); + for (let i = 0; i < cookieArray.length; i++) { + let cookie = cookieArray[i]; + while (cookie.charAt(0) === ' ') { + cookie = cookie.substring(1); + } + if (cookie.indexOf(name) === 0) { + return cookie.substring(name.length, cookie.length); + } + } + return ''; + } + + /** + * This method checks the public cookie for member and individual. + * Reminder: This data is not trusted by the server, so can't be manipulated to get auth. + */ + public static requiresVerification(): boolean { + const parsed = CookieUtil.getUserAuth(); + return !!(parsed.isMember === true && parsed.isIndividual === false); + } + + public static allowLoanResume(): boolean { + const parsed = CookieUtil.getUserAuth(); + return !!(parsed.isMember && parsed.isIndividual && parsed.isTwoFactor); + } + + public static allowPrefill(): boolean { + const parsed = CookieUtil.getUserAuth(); + return !!(parsed.isMember && parsed.isIndividual); + } + + public static getUserAuth(): MemberMeta { + const rawCookie = this.getCookieValue(this.PUBLIC_COOKIE_KEY); + + if (rawCookie) { + const unencoded = atob(rawCookie); + const parsed = JSON.parse(unencoded); + const memberMeta = { + isTwoFactor: parsed.IsTwoFactor, + isMember: parsed.IsMember, + isIndividual: parsed.IsIndividual + }; + return memberMeta; + } + return new MemberMeta(); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/core-translator/core-translator.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/core-translator/core-translator.service.spec.ts new file mode 100644 index 0000000..b65da90 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/core-translator/core-translator.service.spec.ts @@ -0,0 +1,104 @@ +import {ReactiveFormsModule} from '@angular/forms'; +import {TestBed} from '@angular/core/testing'; +import {RouterTestingModule} from '@angular/router/testing'; +import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; +import {NO_ERRORS_SCHEMA} from '@angular/core'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {configureTestSuite} from 'ng-bullet'; +import {CoreTranslatorService} from './core-translator.service'; +import {convertedPersonalInformation, convertedPrimaryAddress, testCoreIndividual} from '../../testing/shared-mocks/test-individual.mock'; +import {AddressInformation} from '../../loan-wizard/field-groups/address/address-information'; + + +describe('Core Translator Service', () => { + + let service: CoreTranslatorService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + CoreTranslatorService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(CoreTranslatorService); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should correctly convert the mock data', () => { + const personalInfo = service.translateCoreToIndividual(testCoreIndividual); + expect(personalInfo).toEqual(convertedPersonalInformation); + }); + + it('should correctly convert the mock primary address data', () => { + const addressInfo = service.translateCoreToPrimaryAddress(testCoreIndividual); + expect(addressInfo).toEqual(convertedPrimaryAddress); + }); + + it('should correctly process a core-zipcode', () => { + const result1 = service.splitZipcode('53027-1981'); + const result2 = service.splitZipcode('53027'); + const result3 = service.splitZipcode(null); + expect(result1).toEqual({ + zipcode: '53027', + ext: '1981' + }); + expect(result2).toEqual({ + zipcode: '53027', + ext: null + }); + expect(result3).toEqual({ + zipcode: null, + ext: null + }); + }); + + it('should correctly convert an address with extended zipcode', () => { + // A sample address as it would arrive from APEX/CORE + const individualAddress = { + 'addressId': 3212176, + 'line1': '1400A SCHAUER DR', + 'line2': '', + 'line3': '', + 'city': 'HARTFORD', + 'state': 'WI', + 'zipCode': '53027-1981', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': true, + 'isInternational': false, + 'addressVerification': 1400, + 'occupancy': 'None', + 'startDate': '0001-01-01T00:00:00', + 'endDate': '0001-01-01T00:00:00', + 'uspsVerified': true + }; + const expectedResult: AddressInformation = { + addressLine1: individualAddress.line1, + addressLine2: individualAddress.line2, + city: individualAddress.city, + state: individualAddress.state, + zipcode: '53027', + zipcodeExt: '1981' + }; + const addressInfo = service.translateCoreToAddress(individualAddress); + expect(addressInfo).toEqual(expectedResult); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/core-translator/core-translator.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/core-translator/core-translator.service.ts new file mode 100644 index 0000000..95f9a34 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/core-translator/core-translator.service.ts @@ -0,0 +1,117 @@ +import {Injectable} from '@angular/core'; +import {CoreIndividual} from 'src/app/member/models/core-individual'; +import {CreditApplication} from '../credit-application'; +import {PhoneTypeEnum} from '../api-models/api-enums'; +import {PersonalInformation} from 'src/app/loan-wizard/field-groups/personal/personal-information'; +import {FormatService} from '../pipes/format.service'; +import {CorePhone} from '../../member/models/core-phone'; +import {AddressInformation} from '../../loan-wizard/field-groups/address/address-information'; +import {CoreAddress} from '../../member/models/core-address'; + +@Injectable({ + providedIn: 'root' +}) +export class CoreTranslatorService { + + + public constructor(private formatService: FormatService) { + } + + public setCoreData(creditApplication: CreditApplication, individual: CoreIndividual): CreditApplication { + Object.assign(creditApplication.fullInfoBorrowers[0], {personalInformation: this.translateCoreToIndividual(individual)}); + const address = this.translateCoreToPrimaryAddress(individual); + if (address) { + Object.assign(creditApplication.fullInfoBorrowers[0], {addressInformation: address}); + } + return creditApplication; + } + + // If it turns out that start and end dates work, we could put them as historical addresses, in order... + public translateCoreToPrimaryAddress(individual: CoreIndividual): AddressInformation { + if (individual && individual.primaryAddressId && individual.address.length > 0) { + const primaryAddressInfo = this.findPrimaryCoreAddress(individual); + if (primaryAddressInfo) { + return this.translateCoreToAddress(primaryAddressInfo); + } + } + return null; + } + + public findPrimaryCoreAddress(individual: CoreIndividual): CoreAddress { + let primaryAddressInfo: CoreAddress = null; + individual.address.forEach((eachAddress: CoreAddress) => { + if (eachAddress.addressId === individual.primaryAddressId) { + primaryAddressInfo = eachAddress; + } + }, this); + return primaryAddressInfo; + } + + public translateCoreToAddress(addressIn: CoreAddress): AddressInformation { + const splitCode = this.splitZipcode(addressIn.zipCode); + const address = { + addressLine1: addressIn.line1, + addressLine2: addressIn.line2, + city: addressIn.city, + state: addressIn.state, + zipcode: addressIn.zipCode + }; + if (splitCode != null && splitCode.zipcode) { + address.zipcode = splitCode.zipcode; + } + if (splitCode != null && splitCode.ext) { + address.zipcodeExt = splitCode.ext; + } + return address; + } + + /** + * Core tends to send zipcode like this: 53027-1981 + * @param zipInput + */ + public splitZipcode(zipInput: string): { zipcode: string, ext: string } { + const result = { + zipcode: zipInput, + ext: null + }; + + if (zipInput && zipInput.indexOf('-') > 0) { + const split = zipInput.split('-'); + result.zipcode = split[0].trim(); + result.ext = split[1].trim(); + } + return result; + } + + /** + * GJF: Will have trouble with these fields: Suffix, Phone Type + * @param individual + */ + public translateCoreToIndividual(individual: CoreIndividual): PersonalInformation { + const basicInfo = { + firstName: individual.firstName ? individual.firstName : '', + middleInitial: individual.middleName ? individual.middleName.charAt(0) : '', + lastName: individual.lastName ? individual.lastName : '', + dateOfBirth: individual.birthDate ? this.formatService.formatDob(individual.birthDate) : '', + socialSecurityNumber: individual.taxId ? individual.taxId : undefined, + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + primaryPhoneNumber: null, + secondaryPhoneNumberType: null, + secondaryPhoneNumber: null, + email: individual.email1 ? individual.email1 : '' + }; + if (individual.phone) { + individual.phone.forEach((eachPhone: CorePhone) => { + if (eachPhone.isPrimaryForType) { + basicInfo.primaryPhoneNumber = eachPhone.areaCode + '-' + eachPhone.phoneNumber; + } else { + basicInfo.secondaryPhoneNumber = eachPhone.areaCode + '-' + eachPhone.phoneNumber; + basicInfo.secondaryPhoneNumberType = PhoneTypeEnum.Mobile; + } + }, this); + } + return basicInfo; + } + + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-dao.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-dao.service.spec.ts new file mode 100644 index 0000000..2199f9f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-dao.service.spec.ts @@ -0,0 +1,126 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from './credit-application-dao.service'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { apiTranslatorPreTranslate } from '../testing/shared-mocks/api-translator-pre-post'; +import { ApplicationWizardService } from '../loan-wizard/application-wizard.service'; +import { CreditApplication } from './credit-application'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { ConfigurationService } from './configuration/configuration.service'; +import { UiSettings } from './configuration/ui-settings'; +import { DefaultSettings } from './configuration/default-settings.mock'; + +describe('CreditApplicationDaoService', () => { + + let service: CreditApplicationDaoService; + let httpMock: HttpTestingController; + let applicationWizardService: ApplicationWizardService; + const creditApplication: CreditApplication = apiTranslatorPreTranslate; + let configService: ConfigurationService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + CreditApplicationDaoService, ApplicationWizardService, + ConfigurationService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(CreditApplicationDaoService); + configService = TestBed.get(ConfigurationService); + configService.saveConfiguration(defaultSettings); + httpMock = TestBed.get(HttpTestingController); + applicationWizardService = TestBed.get(ApplicationWizardService); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should build check decision url', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + spyOn(service, 'buildCheckDecisionUrl').and.callThrough(); + const creditApp = applicationWizardService.getFullCreditApplication(); + const testLoanID = '1234567890'; + creditApp.loanId = testLoanID; + const decisionUrl = service.buildCheckDecisionUrl(creditApp); + expect(service.buildCheckDecisionUrl).toHaveBeenCalled(); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + expect(decisionUrl.includes(testLoanID)).toBe(true); + expect(decisionUrl.includes('DecisioningDetails')).toBe(true); + }); + + it('should build submit decision url', () => { + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + spyOn(service, 'buildSubmitDecisionUrl').and.callThrough(); + const creditApp = applicationWizardService.getFullCreditApplication(); + const testLoanID = '1234567890'; + creditApp.loanId = testLoanID; + const submitDecisionUrl = service.buildSubmitDecisionUrl(creditApp); + expect(service.buildSubmitDecisionUrl).toHaveBeenCalled(); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + expect(submitDecisionUrl.includes(testLoanID)).toBe(true); + expect(submitDecisionUrl.includes('DecisioningDetails')).toBe(true); + }); + + it('should build submit application url', () => { + spyOn(service, 'buildSubmitApplicationUrl').and.callThrough(); + service.buildSubmitApplicationUrl(); + expect(service.buildSubmitApplicationUrl).toHaveBeenCalled(); + }); + + it('should save an application', () => { + spyOn(service, 'saveApplication').and.callThrough(); + spyOn(configService, 'getAllConfiguration').and.returnValue(defaultSettings); + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + const observable = service.saveApplication(applicationWizardService.getFullCreditApplication()); + expect(service.saveApplication).toHaveBeenCalled(); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + expect(observable).toBeTruthy(); + }); + + it('should submit an application', () => { + spyOn(service, 'submitApplication').and.callThrough(); + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + const observable = service.submitApplication(applicationWizardService.getFullCreditApplication()); + expect(service.submitApplication).toHaveBeenCalled(); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + expect(observable).toBeTruthy(); + }); + + it('should decide application', () => { + spyOn(service, 'decideApplication').and.callThrough(); + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + const observable = service.decideApplication(applicationWizardService.getFullCreditApplication()); + expect(service.decideApplication).toHaveBeenCalled(); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + expect(observable).toBeTruthy(); + }); + + it('should get application decision', () => { + spyOn(service, 'getApplicationDecision').and.callThrough(); + spyOn(applicationWizardService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + const observable = service.getApplicationDecision(applicationWizardService.getFullCreditApplication()); + expect(service.getApplicationDecision).toHaveBeenCalled(); + expect(applicationWizardService.getFullCreditApplication).toHaveBeenCalled(); + expect(observable).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-dao.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-dao.service.ts new file mode 100644 index 0000000..610a764 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-dao.service.ts @@ -0,0 +1,197 @@ +import { ErrorHandler, Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { CreditApplication } from './credit-application'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { ApiLoanApplication } from './api-models/api-loan-application'; +import { environment } from '../../environments/environment'; +import { catchError, map } from 'rxjs/operators'; +import { ApiTranslatorService } from './api-translator/api-translator.service'; +import { ApiLoanContainer } from './api-models/api-loan-container'; +import { DecisionStatus } from './decision/decision-status'; +import { DecisionService } from './decision/decision.service'; + +/** + * This class has an important MOCK, so this interface helps ensure the mock matches. + */ +export interface ICreditApplicationDaoService { + saveApplication(creditApplication: CreditApplication): Observable; + + createApplication(creditApplication: CreditApplication): Observable; + + updateApplication(creditApplication: CreditApplication): Observable; + + submitApplication(creditApplication: CreditApplication): Observable; + + decideApplication(creditApplication: CreditApplication): Observable; + + getApplicationDecision(creditApplication: CreditApplication): Observable; +} + +@Injectable({ + providedIn: 'root' +}) +export class CreditApplicationDaoService implements ICreditApplicationDaoService { + + constructor(private httpClient: HttpClient, + private apiTranslatorService: ApiTranslatorService, + private decisionService: DecisionService, + private eHandler: ErrorHandler) { } + + public buildCheckDecisionUrl(creditApplication: CreditApplication): string { + return environment.urlBase + '/LoanApplication/' + creditApplication.loanId + '/DecisioningDetails'; + } + + public buildSubmitDecisionUrl(creditApplication: CreditApplication): string { + return environment.urlBase + '/LoanApplication/' + creditApplication.loanId + '/DecisioningDetails'; + } + + public buildSubmitApplicationUrl(): string { + return environment.urlBase + '/LoanApplication/Submit'; + } + + public buildStorageUrl(): string { + return environment.urlBase + '/Storage'; + } + + public saveApplication(creditApplication: CreditApplication): Observable { + if (creditApplication.loanId) { + return this.updateApplication(creditApplication); + } else { + return this.createApplication(creditApplication); + } + } + + public createEmptyApplication(creditApplication: CreditApplication): Observable { + const loanContainer = this.commandBuildApiLoanContainer(creditApplication); + const hiddenPersonalInfoLoanApplication = this.commandBuildApiLoanApplicationObstructedFields(creditApplication); + let observable: Observable; + observable = this.httpClient.post(this.buildStorageUrl() + "/New", loanContainer, {withCredentials: true}) + .pipe(map(result => { + return result; + }), + catchError(err => { + // @TODO ERROR_NOTIFY + if (err.status === 500) { + throw new Error('500: Server could not process API response'); + } else { + throw new Error('Bad request'); + } + }) + ); + return observable; + } + + public createApplication(creditApplication: CreditApplication): Observable { + const loanContainer = this.commandBuildApiLoanContainer(creditApplication); + const hiddenPersonalInfoLoanApplication = this.commandBuildApiLoanApplicationObstructedFields(creditApplication); + let observable: Observable; + observable = this.httpClient.post(this.buildStorageUrl(), loanContainer, {withCredentials: true}) + .pipe(map(result => { + return result; + }), + catchError(err => { + // @TODO ERROR_NOTIFY + if (err.status === 500) { + throw new Error('500: Server could not process API response'); + } else { + throw new Error('Bad request'); + } + }) + ); + return observable; + } + + public updateApplication(creditApplication: CreditApplication): Observable { + const loanContainer = this.commandBuildApiLoanContainer(creditApplication); + const hiddenPersonalInfoLoanApplication = this.commandBuildApiLoanApplicationObstructedFields(creditApplication); + return this.httpClient.put(this.buildStorageUrl(), loanContainer, {withCredentials: true}) + .pipe( + catchError(err => { + // @TODO ERROR_NOTIFY + if (err.status === 500) { + throw new Error('500: Server could not process API response'); + } else { + throw new Error('Bad request'); + } + })); + } + + public submitApplication(creditApplication: CreditApplication): Observable { + const hiddenPersonalInfoLoanApplication = this.commandBuildApiLoanApplicationObstructedFields(creditApplication); + let observable: Observable; + observable = this.httpClient.post(this.buildSubmitApplicationUrl() + '/' + creditApplication.loanId, '', {withCredentials: true}) + .pipe(map(result => { + return result; + }), + catchError(err => { + // @TODO ERROR_NOTIFY + if (err.status === 500) { + throw new Error('500: Server could not process API response'); + } else if (err.status === 520) { + throw new Error('520: Server could not communicate with CRIF'); + } else { + throw new Error('Bad request'); + } + }) + ); + return observable; + } + + public decideApplication(creditApplication: CreditApplication): Observable { + return this.httpClient.put(this.buildSubmitDecisionUrl(creditApplication), + { + 'decisioningState': 'Requested' + }) + .pipe(map(result => { + return result; + }), + catchError(err => { + // @TODO ERROR_NOTIFY + if (err.status === 500) { + throw new Error('500: Server could not process API response'); + } else if (err.status === 520) { + throw new Error('520: Server could not communicate with CRIF'); + } else { + throw new Error('Bad request'); + } + }) + ); + } + + public getApplicationDecision(creditApplication: CreditApplication): Observable { + return this.httpClient.get(this.buildCheckDecisionUrl(creditApplication)) + .pipe(map(result => { + const dStatus = { + state: result['decisioningState'], + result: result['decisioningResult'], + stipulations: result['stipulations'] + }; + + return this.decisionService.createDecisionResult(dStatus, creditApplication.loanType.shouldDecision); + }), + catchError(err => { + // @TODO ERROR_NOTIFY + if (err.status === 500) { + throw new Error('500: Server could not process API response'); + } else if (err.status === 520) { + throw new Error('520: Server could not communicate with CRIF'); + } else { + throw new Error('Bad request'); + } + }) + ); + } + + public commandBuildApiLoanContainer(application: CreditApplication): ApiLoanContainer { + return this.apiTranslatorService.translateToApiLoanContainer(application); + } + + public commandBuildApiLoanApplication(application: CreditApplication): ApiLoanApplication { + return this.apiTranslatorService.translateToApiLoanApplication(application); + } + + public commandBuildApiLoanApplicationObstructedFields(application: CreditApplication): ApiLoanApplication { + return this.apiTranslatorService.translateToApiLoanApplicationHiddenValues(application); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-values.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-values.ts new file mode 100644 index 0000000..b617b58 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application-values.ts @@ -0,0 +1,9 @@ +export class CreditApplicationValues { + public static readonly STATE_REQUIRED_COAPP = 'WI'; + public static readonly CHOICE_YES = 'yes'; + public static readonly CHOICE_NO = 'no'; + public static readonly VEHICLE_CUT_OFF_YEAR_LOWER = 1900; + public static readonly VEHICLE_CUT_OFF_YEAR_UPPER = 2030; + public static readonly VEHICLE_CURRENT_YEAR = 2023; + public static readonly UNDEFINED = 'undefined'; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.spec.ts new file mode 100644 index 0000000..07787b2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.spec.ts @@ -0,0 +1,422 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CreditApplicationDaoService } from './credit-application-dao.service'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { CreditApplicationService } from './credit-application.service'; +import { configureTestSuite } from 'ng-bullet'; +import { LoanType, LoanTypeSet } from './loan-type'; +import { LoanTitle } from './loan-type/loan-title'; +import { CreditApplicationValues } from './credit-application-values'; +import { of } from 'rxjs'; +import { apiTranslatorPreTranslate } from '../testing/shared-mocks/api-translator-pre-post'; +import { EthnicityInformation } from '../loan-wizard/field-groups/hmda/ethnicity-information'; +import { GenderInformation } from '../loan-wizard/field-groups/hmda/gender-information'; +import { RaceInformation } from '../loan-wizard/field-groups/hmda/race-information'; +import { BorrowerType } from './borrower-type'; +import { EmploymentInformation } from '../loan-wizard/field-groups/employment/employment-information'; +import { IncomeInformation } from '../loan-wizard/field-groups/employment/income-information'; +import { AlimonyInformation } from '../loan-wizard/field-groups/employment/alimony-information'; +import { CreditApplication, SpouseType, TattleIndividual } from './credit-application'; +import { AddressInformation } from '../loan-wizard/field-groups/address/address-information'; +import { ResidenceInformation } from '../loan-wizard/field-groups/address/residence-information'; +import { PersonalInformation } from '../loan-wizard/field-groups/personal/personal-information'; +import { IsFullInfoSpouse } from '../loan-wizard/field-groups/spouse-type/is-full-info-spouse-information'; +import { PhoneTypeEnum, PropertyTypeEnum } from './api-models/api-enums'; +import { MembershipInformation } from '../loan-wizard/field-groups/member/membership-information'; +import { primaryAndCoApplicationAddressSameApplication } from '../testing/shared-mocks/broken-applications'; +import { ConfigurationService } from './configuration/configuration.service'; +import { UiSettings } from './configuration/ui-settings'; +import { DefaultSettings } from './configuration/default-settings.mock'; + +describe('CreditApplicationService', () => { + + let creditApplicationService: CreditApplicationService; + let creditApplicationDaoService: CreditApplicationDaoService; + let httpMock: HttpTestingController; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + CreditApplicationDaoService + ] + }); + }); + + beforeEach(() => { + creditApplicationService = TestBed.get(CreditApplicationService); + creditApplicationDaoService = TestBed.get(CreditApplicationDaoService); + httpMock = TestBed.get(HttpTestingController); + creditApplicationService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(creditApplicationService).toBeTruthy(); + }); + + it('should save term of loan for equity member', () => { + creditApplicationService.setLoanType(LoanTypeSet[4].id); + spyOn(creditApplicationService, 'getFullCreditApplication').and.callThrough(); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + const testStub = { + 'applicantType': CreditApplicationValues.CHOICE_NO, + 'limitRequested': '1000', + 'termOfLoan': 120, + 'maritalStatus': CreditApplicationValues.CHOICE_NO, + 'clientMessage': 'test message' + }; + creditApplicationService.saveEquityMemberInformation(testStub); + const creditApplication = creditApplicationService.getFullCreditApplication(); + expect(creditApplicationService.getFullCreditApplication).toHaveBeenCalled(); + expect(creditApplication.termOfLoan).toBeGreaterThanOrEqual(120); + expect(creditApplication.clientMessage).toEqual(testStub.clientMessage); + }); + + it('should save HMDA information', () => { + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + const ethnicity: EthnicityInformation = { + cuban: true, + otherHispanicLatinoText: 'San Lorenzoan' + }; + const sex: GenderInformation = { + male: true, + female: true + }; + const race: RaceInformation = { + filipino: true, + americanIndianAlaskaNativeText: 'False Karass' + }; + const hmda = { + ethnicityInformation: ethnicity, + genderInformation: sex, + raceInformation: race + }; + creditApplicationService.saveHMDAInformation(hmda, BorrowerType.CO_BORROWER); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[1].ethnicityInformation).toEqual(ethnicity); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[1].genderInformation).toEqual(sex); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[1].raceInformation).toEqual(race); + }); + + it('should set loantype if existing credit application has different loantype', () => { + const result = creditApplicationService.shouldSetupCreditApplication(LoanTypeSet[1]); + expect(result).toBe(true); + }); + + it('should NOT set loantype if existing credit application has same loantype', () => { + const result = creditApplicationService.shouldSetupCreditApplication(LoanTypeSet[0]); + expect(result).toBe(false); + }); + + it('should save and process employment/income information, only once', () => { + const testEmploymentInformation: Array = [{ + employerName: 'emp1', + employmentType: '3', + jobTitle: 'senior qa', + salary: 5000, + schedule: 'Hourly', + employmentLengthYears: 5, + employmentLengthMonths: 0, + workPhoneNumber: '' + }]; + const testIncomeInformation: Array = [{ + incomeName: 'Pension', + amount: 4000 + }, + { + incomeName: 'Gambling', + amount: 10000 + }, + { + incomeName: 'Check from parents', + amount: 20000 + }]; + const testAlimonyInformaiton: AlimonyInformation = { + childSupportAmount: 3000 + }; + spyOn(creditApplicationService, 'saveEmploymentInformation').and.callThrough(); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + + creditApplicationService.saveAllEmploymentInformation( + testEmploymentInformation, testIncomeInformation, testAlimonyInformaiton, BorrowerType.PRIMARY_BORROWER); + // Bug #840, Make sure calling twice doesn't ADD + creditApplicationService.saveAllEmploymentInformation( + testEmploymentInformation, testIncomeInformation, testAlimonyInformaiton, BorrowerType.PRIMARY_BORROWER); + const employmentInfo = creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].employmentInformation; + const otherIncome = creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].otherIncomeInformation; + const alimony = creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].alimonyInformation; + expect(creditApplicationService.getFullCreditApplication().page.hasEmployment).toBe(true); + expect(employmentInfo).toBeTruthy(); + expect(employmentInfo.length).toEqual(1); // Called twice, should only be one. + expect(employmentInfo[0].jobTitle).toEqual('senior qa'); + expect(employmentInfo[0].employerName).toEqual('emp1'); + expect(employmentInfo[0].salary).toEqual(5000); + expect(otherIncome[0].amount).toEqual(4000); + expect(otherIncome[0].incomeName).toBe('Pension'); + expect(otherIncome[1].amount).toEqual(10000); + expect(otherIncome[1].incomeName).toBe('Gambling'); + expect(otherIncome[2].amount).toEqual(20000); + expect(otherIncome[2].incomeName).toBe('Check from parents'); + expect(alimony.childSupportAmount).toEqual(3000); + }); + + // tslint:disable-next-line:max-line-length + it('should remove spouseType from a fullInfoBorrower who has changed state of residence such that spouse information is not required', () => { + const marriedPrimaryApplication: CreditApplication = apiTranslatorPreTranslate; + marriedPrimaryApplication.fullInfoBorrowers[0].fullInfoSpouse = apiTranslatorPreTranslate.fullInfoBorrowers[1]; + marriedPrimaryApplication.fullInfoBorrowers[0].spouseType = SpouseType.FullInfoSpouse; + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(marriedPrimaryApplication); + const addressInformation: AddressInformation = marriedPrimaryApplication.fullInfoBorrowers[0].addressInformation; + addressInformation.state = 'IL'; + creditApplicationService.saveAllAddressInformation(BorrowerType.PRIMARY_BORROWER, addressInformation); + expect(creditApplicationService.getFullCreditApplication().page.hasAddress).toBe(true); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].spouseType).toBeFalsy(); + // This is an 'after' hook + addressInformation.state = CreditApplicationValues.STATE_REQUIRED_COAPP; + creditApplicationService.saveAllAddressInformation(BorrowerType.PRIMARY_BORROWER, addressInformation); + }); + + it('should save address and residence history', () => { + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + + const addressInformation: AddressInformation = apiTranslatorPreTranslate.fullInfoBorrowers[0].addressInformation; + const residenceInformation: ResidenceInformation = apiTranslatorPreTranslate.fullInfoBorrowers[0].residenceInformation; + const priorAddress: Array = [apiTranslatorPreTranslate.fullInfoBorrowers[1].addressInformation]; + const priorResidence: Array = [apiTranslatorPreTranslate.fullInfoBorrowers[1].residenceInformation]; + creditApplicationService.saveAddressAndResidenceInformation({ + addressInfo: addressInformation, + residenceInfo: residenceInformation, + priorAddressInfo: priorAddress, + priorResidenceInfo: priorResidence + }, BorrowerType.CO_BORROWER); + const result = creditApplicationService.getFullCreditApplication(); + expect(result.fullInfoBorrowers[1].addressInformation).toEqual(addressInformation); + expect(result.fullInfoBorrowers[1].residenceInformation).toEqual(residenceInformation); + expect(result.fullInfoBorrowers[1].priorAddressInformation).toEqual(priorAddress); + expect(result.fullInfoBorrowers[1].priorResidenceInformation).toEqual(priorResidence); + }); + + it('should save mailing address when it differs from current address', () => { + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + + const PRIMARY = 0; + const mailingAddress = { + addressLine1: '200 S Wacker', + addressLine2: 'Dr # 3600', + zipcode: '60606', + city: 'Chicago', + state: 'IL' + }; + apiTranslatorPreTranslate.fullInfoBorrowers[PRIMARY].residenceInformation.isMailingAddressSame = false; + apiTranslatorPreTranslate.fullInfoBorrowers[PRIMARY].mailingAddressInformation = mailingAddress; + const addressInformation: AddressInformation = apiTranslatorPreTranslate.fullInfoBorrowers[PRIMARY].addressInformation; + const residenceInformation: ResidenceInformation = apiTranslatorPreTranslate.fullInfoBorrowers[PRIMARY].residenceInformation; + const mailingInformation: AddressInformation = apiTranslatorPreTranslate.fullInfoBorrowers[PRIMARY].mailingAddressInformation; + creditApplicationService.saveAddressAndResidenceInformation({ + addressInfo: addressInformation, + residenceInfo: residenceInformation, + mailingInfo: mailingInformation + }, BorrowerType.PRIMARY_BORROWER); + const result = creditApplicationService.getFullCreditApplication(); + expect(result.fullInfoBorrowers[PRIMARY].addressInformation).toEqual(addressInformation); + expect(result.fullInfoBorrowers[PRIMARY].mailingAddressInformation).toEqual(mailingInformation); + expect(result.fullInfoBorrowers[PRIMARY].residenceInformation).toEqual(residenceInformation); + }); + + it('should determine if there is enough data to save ', () => { + const application = creditApplicationService.getFullCreditApplication(); + application.fullInfoBorrowers[0].personalInformation.firstName = 'Bob'; + application.fullInfoBorrowers[0].addressInformation.addressLine1 = '1235 Maple Ave'; + const result = creditApplicationService.hasMinimumSaveInformation(); + expect(result).toBe(true); + }); + + it('should nullify previously entered spouse information when an applicant changes their marital status from YES to NO', () => { + const marriedPrimaryApplication: CreditApplication = apiTranslatorPreTranslate; + marriedPrimaryApplication.fullInfoBorrowers[0].fullInfoSpouse = apiTranslatorPreTranslate.fullInfoBorrowers[1]; + marriedPrimaryApplication.fullInfoBorrowers[0].spouseType = SpouseType.FullInfoSpouse; + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(marriedPrimaryApplication); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].spouseType).toBe(SpouseType.FullInfoSpouse); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].fullInfoSpouse).toBeTruthy(); + const personalInfo: PersonalInformation = { + firstName: 'Someone', + lastName: 'who changed their mind', + dateOfBirth: 'about being married', + primaryPhoneNumber: 'after already', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + socialSecurityNumber: 'submitting a bunch of information about an imaginary spouse', + email: 'notMarried@forever_alone.com', + maritalStatus: CreditApplicationValues.CHOICE_NO + }; + creditApplicationService.savePersonalInformation(personalInfo, BorrowerType.PRIMARY_BORROWER); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].spouseType).toBeFalsy(); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].fullInfoSpouse).toBeFalsy(); + }); + + it('should nullify previously entered spouse information when an applicant changes their marital status from NO to co-applicant', () => { + const marriedPrimaryApplication: CreditApplication = apiTranslatorPreTranslate; + marriedPrimaryApplication.fullInfoBorrowers[0].fullInfoSpouse = apiTranslatorPreTranslate.fullInfoBorrowers[1]; + marriedPrimaryApplication.fullInfoBorrowers[0].spouseType = SpouseType.FullInfoSpouse; + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(marriedPrimaryApplication); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].spouseType).toBe(SpouseType.FullInfoSpouse); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].fullInfoSpouse).toBeTruthy(); + const personalInfo: PersonalInformation = { + firstName: 'Someone', + lastName: 'who changed their mind', + dateOfBirth: 'about being married', + primaryPhoneNumber: 'after already', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + socialSecurityNumber: 'submitting a bunch of information about an imaginary spouse', + email: 'notMarried@forever_alone.com', + maritalStatus: CreditApplicationValues.CHOICE_YES, + coApplicantSpouse: CreditApplicationValues.CHOICE_YES + }; + creditApplicationService.savePersonalInformation(personalInfo, BorrowerType.PRIMARY_BORROWER); + expect(creditApplicationService.getFullCreditApplication().page.hasPersonal).toBe(true); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].spouseType).toBeFalsy(); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].fullInfoSpouse).toBeFalsy(); + }); + + it('should save tattle spouse information', () => { + const marriedPrimaryApplication: CreditApplication = apiTranslatorPreTranslate; + const tattleSpouse: TattleIndividual = { + personalInformation: { + firstName: 'Tattly', + lastName: 'McSpouseface' + } + }; + + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(marriedPrimaryApplication); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + + creditApplicationService.saveTattleSpouseInformation(tattleSpouse, BorrowerType.PRIMARY_BORROWER_SPOUSE); + + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].tattleSpouse).toBeTruthy(); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].tattleSpouse).toEqual(tattleSpouse); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[0].fullInfoSpouse).toBeFalsy(); + }); + + it('should set spouseType information', () => { + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + + const spouseType: IsFullInfoSpouse = { + isFullInfoSpouse: CreditApplicationValues.CHOICE_YES + }; + creditApplicationService.saveSpouseTypeInformation(spouseType, BorrowerType.PRIMARY_BORROWER); + expect(creditApplicationService.getSpouseType(BorrowerType.PRIMARY_BORROWER)).toBe(SpouseType.FullInfoSpouse); + expect(creditApplicationService.getIndividualGroupValues('addressInformation', BorrowerType.PRIMARY_BORROWER_SPOUSE)).toBeTruthy(); + }); + + it('should save an application ', () => { + const application = creditApplicationService.getFullCreditApplication(); + application.fullInfoBorrowers[0].personalInformation.firstName = 'Bob'; + application.fullInfoBorrowers[0].addressInformation.addressLine1 = '1235 Maple Ave'; + spyOn(creditApplicationDaoService, 'saveApplication').and.returnValue(of(true)); + creditApplicationService.saveCreditApplication(); + expect(creditApplicationDaoService.saveApplication).toHaveBeenCalled(); + }); + + it('should gather member information from the application', () => { + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + const expectedReturn: MembershipInformation = { + limitRequested: apiTranslatorPreTranslate.limitRequested, + applicantType: apiTranslatorPreTranslate.hasCoApplicant, + livesInMaritalPropertyState: CreditApplicationValues.CHOICE_YES, + confirmJointCredit: apiTranslatorPreTranslate.confirmJointCredit, + coApplicantLivesInMaritalPropertyState: CreditApplicationValues.CHOICE_YES, + purposeOfLoanSignature: apiTranslatorPreTranslate.purposeOfLoanSignature, + agreementRequirement: undefined + }; + expect(creditApplicationService.getMemberInformation()).toEqual(expectedReturn); + }); + + it('should determine that an applicant (or coapplicant) is married', () => { + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(apiTranslatorPreTranslate); + expect(creditApplicationService.isMarriedApplicant()).toBe(true); + spyOn(creditApplicationService, 'getIndividualByBorrowerType').and.returnValue({ + personalInformation: { + maritalStatus: CreditApplicationValues.CHOICE_NO + } + }); + expect(creditApplicationService.isMarriedApplicant()).toBe(false); + }); + + it('should handle situations where applications change their mind about co-applicant spouse', () => { + const marriedPrimaryApplication: any = primaryAndCoApplicationAddressSameApplication; + + spyOn(creditApplicationService, 'getFullCreditApplication').and.returnValue(marriedPrimaryApplication); + spyOn(creditApplicationService, 'saveCreditApplication').and.returnValue(of(true)); + expect(creditApplicationService.getFullCreditApplication() + .fullInfoBorrowers[1].hasSameAddressAsSpouse).toEqual(CreditApplicationValues.CHOICE_YES); + + const personalInfo: PersonalInformation = Object.assign({}, + primaryAndCoApplicationAddressSameApplication.fullInfoBorrowers[0].personalInformation); + personalInfo.coApplicantSpouse = CreditApplicationValues.CHOICE_NO; + creditApplicationService.savePersonalInformation(personalInfo, BorrowerType.PRIMARY_BORROWER); + expect(creditApplicationService.getFullCreditApplication().fullInfoBorrowers[1].hasSameAddressAsSpouse).toBeNull(); + + }); + + it('should set Estimated Value & Property Type defaults on HELOC Rate Mod loan type', () => { + const loanConfigurationService = TestBed.get(ConfigurationService); + loanConfigurationService.saveConfiguration(DefaultSettings as UiSettings); + const helocRateMod = LoanTypeSet.find((loanType) => loanType.title === LoanTitle.HELOC_RateMod); + if (helocRateMod) { + creditApplicationService.setLoanType(helocRateMod.id); + } else { + creditApplicationService.setLoanType(LoanTypeSet[0].id); + const application = creditApplicationService.getFullCreditApplication(); + application.loanType = { + ...application.loanType, + title: LoanTitle.HELOC_RateMod + }; + } + creditApplicationService.saveCollateralInformation( + { + "collateralInformation": { + "isAddressSame": "yes", + "estimatedValue": 100000, + "propertyType": "Duplex", + "isBeingImproved": "false", + "isListedForSale": "yes", + "occupancyType": "OwnerOccupied", + "hasFirstMortgage": "true", + "hasSecondMortgage": "false", + "payoffFirstMortgage": "false", + "addressLine1": "Fake Street 123", + "addressLine2": "", + "zipcode": "66666", + "city": "Test Town", + "state": "WI", + "residenceStatus": "0", + "residenceTimeYears": "2", + "residenceTimeMonths": "0", + "residenceMonthlyPayment": "2000", + "isMailingAddressSame": true + } + } + ); + const creditApplication = creditApplicationService.getFullCreditApplication(); + expect(creditApplication.loanType.title).toEqual(LoanTitle.HELOC_RateMod); + expect(creditApplication.collateralInformation.estimatedValue).toEqual(250000); + expect(creditApplication.collateralInformation.propertyType).toEqual(PropertyTypeEnum[PropertyTypeEnum.SingleFamily]); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.ts new file mode 100644 index 0000000..8d76cb3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.service.ts @@ -0,0 +1,871 @@ +import {Injectable} from '@angular/core'; +import {LoanType, LoanTypeSet} from './loan-type'; +import {CreditApplication, Individual, SpouseType, TattleIndividual} from './credit-application'; +import {DisclosureInformation} from '../loan-wizard/field-groups/disclosures/credit-card-disclosure'; +import {AddressInformation} from '../loan-wizard/field-groups/address/address-information'; +import {ResidenceInformation} from '../loan-wizard/field-groups/address/residence-information'; +import {PersonalInformation} from '../loan-wizard/field-groups/personal/personal-information'; +import {GenderInformation} from '../loan-wizard/field-groups/hmda/gender-information'; +import {EthnicityInformation} from '../loan-wizard/field-groups/hmda/ethnicity-information'; +import {RaceInformation} from '../loan-wizard/field-groups/hmda/race-information'; +import {AdditionalInformation} from '../loan-wizard/field-groups/summary/additional-info'; +import {TattleSpousePersonalInformation} from '../loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-information'; +import {ApiPage} from './api-models/api-page'; +import {ApiStatus} from './api-models/api-status'; +import {ApiError, ApiErrorCode} from './api-models/api-error'; +import {FrequencyTypeEnum, PhoneTypeEnum, PropertyTypeEnum} from './api-models/api-enums'; +import {Observable, of, Subject} from 'rxjs'; +import {MaskingService} from './masking/masking.service'; +import {CreditApplicationValues} from './credit-application-values'; +import {CreditApplicationDaoService} from './credit-application-dao.service'; +import {catchError, map} from 'rxjs/operators'; +import {MembershipInformation} from '../loan-wizard/field-groups/member/membership-information'; +import {BorrowerType} from './borrower-type'; +import {EmploymentInformation} from '../loan-wizard/field-groups/employment/employment-information'; +import {IncomeInformation} from '../loan-wizard/field-groups/employment/income-information'; +import {AlimonyInformation} from '../loan-wizard/field-groups/employment/alimony-information'; +import {VehicleCollateralInformation} from '../loan-wizard/field-groups/vehicle/vehicle-collateral-information'; +import {IsFullInfoSpouse} from '../loan-wizard/field-groups/spouse-type/is-full-info-spouse-information'; +import {EquityMemberInformation} from '../loan-wizard/field-groups/equity/equity-member-information'; +import {LoanPurpose} from './loan-type/loan-purpose'; +import {LoanTitle} from './loan-type/loan-title'; +import { MemberInfoFormService } from '../loan-wizard/field-groups/member/member-info-form.service'; + +@Injectable({ + providedIn: 'root' +}) +export class CreditApplicationService { + + protected creditApplication: CreditApplication; + public memberService: MemberInfoFormService; + private loanTypeSubject: Subject = new Subject(); + private loanType$ = this.loanTypeSubject.asObservable(); + + // DEFAULT HELOC RATE MOD VALUES + private readonly defaultHELOCestimatedValue = 250000; + private readonly defaultHELOCpropertyType = PropertyTypeEnum[PropertyTypeEnum.SingleFamily]; + + constructor(private creditApplicationDaoService: CreditApplicationDaoService, + ) {} + + public setLoanType(loanTypeValue: string, parsedData?: any): void { + const loanType = this.getLoanTypeForValue(loanTypeValue); + if (!loanType) { + throw new Error('Could not lookup loan type for ' + loanTypeValue); + } + if (this.shouldSetupCreditApplication(loanType)) { + this.creditApplication = this.constructCreditApplicationByType(loanType, parsedData); + this.loanTypeSubject.next(this.creditApplication.loanType); + } + } + + public shouldSetupCreditApplication(loanType: LoanType): boolean { + if (this.getFullCreditApplication()) { + if (this.getFullCreditApplication().loanType !== loanType) { + return true; + } else { + return false; + } + } else { + return true; + } + } + + public resetLoanType(): void { + this.loanTypeSubject.next(null); + } + + public getLoanTypeObservable(): Observable { + return this.loanType$; + } + + public getFullCreditApplication(): CreditApplication { + return this.creditApplication; + } + + public getIndividualByBorrowerType(individual: BorrowerType) { + const creditApplication = this.getFullCreditApplication(); // DO NOT REMOVE THIS, tests will fail. + switch (individual) { + case BorrowerType.PRIMARY_BORROWER: + return creditApplication.fullInfoBorrowers[0]; + case BorrowerType.CO_BORROWER: + this.addDefaultCoApplicantIfNecessary(); + return creditApplication.fullInfoBorrowers[1]; + case BorrowerType.PRIMARY_BORROWER_SPOUSE: + return this.getIndividualSpouse(creditApplication.fullInfoBorrowers[0]); + case BorrowerType.CO_BORROWER_SPOUSE: + return this.getIndividualSpouse(creditApplication.fullInfoBorrowers[1]); + } + throw new Error('Invalid individual type in getIndividualByBorrowerType: ' + individual); + } + + public getIndividualSpouse(individual: Individual): any { + if (individual.spouseType === SpouseType.FullInfoSpouse) { + return individual.fullInfoSpouse; + } else { + return individual.tattleSpouse; + } + } + + public isCoApplicantSpouse(): boolean { + return this.getFullCreditApplication().hasCoApplicantSpouse === CreditApplicationValues.CHOICE_YES; + } + + public addDefaultCoApplicantIfNecessary() { + const creditApplication = this.getFullCreditApplication(); + if (creditApplication.hasCoApplicant === CreditApplicationValues.CHOICE_YES + && creditApplication.fullInfoBorrowers.length === 1) { + creditApplication.fullInfoBorrowers.push(this.generateDefaultFullInfoBorrower()); + if (creditApplication.coApplicantLivesInMaritalPropertyState) { + creditApplication.fullInfoBorrowers[1].addressInformation.state = CreditApplicationValues.STATE_REQUIRED_COAPP; + } + if (this.isCoApplicantSpouse()) { + creditApplication.fullInfoBorrowers[1].personalInformation.maritalStatus = CreditApplicationValues.CHOICE_YES; + } + } + } + + public getSpouseType(borrowerType: BorrowerType): SpouseType { + const individual = this.getIndividualByBorrowerType(borrowerType); + if (borrowerType === BorrowerType.PRIMARY_BORROWER || (borrowerType === BorrowerType.CO_BORROWER && individual)) { + return individual.spouseType; + } + return null; + } + + public getIndividualGroupValues(group: string, individual: BorrowerType) { + const selectedIndividual = this.getIndividualByBorrowerType(individual); + if (selectedIndividual && selectedIndividual[group]) { + return selectedIndividual[group]; + } + return {}; + } + + public getEquityMemberInformation(): EquityMemberInformation { + const creditApplication: CreditApplication = this.getFullCreditApplication(); + return { + purposeOfLoan: creditApplication.purposeOfLoan || LoanPurpose.Unselected, + termOfLoan: creditApplication.termOfLoan + }; + } + + public getMemberInformation(): MembershipInformation { + const creditApplication: CreditApplication = this.getFullCreditApplication(); + let livesInMaritalPropertyState = null; + const stateOfResidence = creditApplication.fullInfoBorrowers[0].addressInformation.state; + if (stateOfResidence) { + livesInMaritalPropertyState = stateOfResidence === CreditApplicationValues.STATE_REQUIRED_COAPP ? + CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO; + } + const memberInfo: MembershipInformation = { + limitRequested: creditApplication.limitRequested ? creditApplication.limitRequested : 0, + applicantType: creditApplication.hasCoApplicant, + livesInMaritalPropertyState: livesInMaritalPropertyState, + confirmJointCredit: creditApplication.confirmJointCredit, + purposeOfLoanSignature: creditApplication.purposeOfLoanSignature, + agreementRequirement: creditApplication.agreementRequirement + }; + if (memberInfo.applicantType === CreditApplicationValues.CHOICE_YES) { + memberInfo.coApplicantLivesInMaritalPropertyState = creditApplication.coApplicantLivesInMaritalPropertyState ? + CreditApplicationValues.CHOICE_YES : CreditApplicationValues.CHOICE_NO; + } + if (this.isHelocRateMod(creditApplication.loanType.title)) { + memberInfo.applicantType = CreditApplicationValues.CHOICE_NO; + memberInfo.livesInMaritalPropertyState = CreditApplicationValues.CHOICE_YES; + } + return memberInfo; + } + + public getPrimaryIndividualIfSpouseBorrowerType(borrowerType: BorrowerType): Individual { + const creditApplication = this.getFullCreditApplication(); // DO NOT REMOVE THIS, tests will fail. + switch (borrowerType) { + case BorrowerType.PRIMARY_BORROWER_SPOUSE: + return creditApplication.fullInfoBorrowers[0]; + case BorrowerType.CO_BORROWER_SPOUSE: + return creditApplication.fullInfoBorrowers[1]; + case BorrowerType.PRIMARY_BORROWER: + return null; + case BorrowerType.CO_BORROWER: + return null; + } + throw new Error('Invalid individual type in getPrimaryIndividualIfSpouseBorrowerType: ' + borrowerType); + } + + public saveSpouseTypeInformation(spouseType: IsFullInfoSpouse, individual: BorrowerType): Observable { + const creditApplication = this.getFullCreditApplication(); + const setIndividual = this.getIndividualByBorrowerType(individual); + if (spouseType.isFullInfoSpouse === CreditApplicationValues.CHOICE_YES && setIndividual.spouseType !== SpouseType.FullInfoSpouse) { + setIndividual.spouseType = SpouseType.FullInfoSpouse; + setIndividual.fullInfoSpouse = this.generateDefaultFullInfoBorrower(); + setIndividual.tattleSpouse = null; + } else if (spouseType.isFullInfoSpouse === CreditApplicationValues.CHOICE_NO && setIndividual.spouseType !== SpouseType.TattleSpouse) { + setIndividual.spouseType = SpouseType.TattleSpouse; + setIndividual.tattleSpouse = this.generateDefaultTattleSpouse(); + setIndividual.fullInfoSpouse = null; + } + creditApplication.page.hasSpouseInfo = true; + return this.saveCreditApplication(); + } + + public saveTattleSpouseInformation(tattleSpouseInfo: TattleIndividual, borrowerType: BorrowerType): Observable { + const creditApplication = this.getFullCreditApplication(); + const parentIndividual: Individual = this.getPrimaryIndividualIfSpouseBorrowerType(borrowerType); + parentIndividual.tattleSpouse = tattleSpouseInfo; + parentIndividual.fullInfoSpouse = null; + creditApplication.page.hasTattleSpouseInfo = true; + return this.saveCreditApplication(); + } + + public savePersonalInformation(personalInfo: PersonalInformation, borrowerType: BorrowerType): Observable { + const creditApplication = this.getFullCreditApplication(); + const currentIndividual = this.getIndividualByBorrowerType(borrowerType); + if (personalInfo.coApplicantSpouse === CreditApplicationValues.CHOICE_YES) { + creditApplication.hasCoApplicantSpouse = CreditApplicationValues.CHOICE_YES; + const primaryBorrower = this.getIndividualByBorrowerType(BorrowerType.PRIMARY_BORROWER); + primaryBorrower.spouseType = null; + primaryBorrower.fullInfoSpouse = null; + primaryBorrower.tattleSpouse = null; + } else if (personalInfo.coApplicantSpouse === CreditApplicationValues.CHOICE_NO) { + creditApplication.hasCoApplicantSpouse = CreditApplicationValues.CHOICE_NO; + // GJF - if the person returns and changes their mind about their spouse, we have to reset this flag. #767 + if (borrowerType === BorrowerType.PRIMARY_BORROWER && creditApplication.fullInfoBorrowers.length > 1) { + creditApplication.fullInfoBorrowers[1].hasSameAddressAsSpouse = null; + } + // how do I get this person's spouse? + // reset the hasSameAddressAsSpouse flag. + } + if (personalInfo.maritalStatus === CreditApplicationValues.CHOICE_NO) { + currentIndividual.spouseType = null; + currentIndividual.tattleSpouse = null; + currentIndividual.fullInfoSpouse = null; + } + currentIndividual.personalInformation = personalInfo; + // GJF - This is determining if the maritalStatus should be forced to yes for a co-applicant. + if (this.getPrimaryIndividualIfSpouseBorrowerType(borrowerType)) { + currentIndividual.personalInformation.maritalStatus = CreditApplicationValues.CHOICE_YES; + } + creditApplication.page.hasPersonal = true; + return this.saveCreditApplication(); + } + + public saveAllEmploymentInformation( + employmentHistory: Array = [], + otherIncomes: Array = [], + alimonyInfo: AlimonyInformation = null, + borrowerType: BorrowerType): Observable { + const creditApplication = this.getFullCreditApplication(); + const setIndividual: Individual = this.getIndividualByBorrowerType(borrowerType); + + this.saveEmploymentInformation(employmentHistory, borrowerType); + this.saveOtherIncomeInformation(otherIncomes, borrowerType); + + if (alimonyInfo) { + setIndividual.alimonyInformation = alimonyInfo; + setIndividual.alimonyInformation.childSupportAmount = MaskingService.unMaskDecimalNumber(alimonyInfo.childSupportAmount); + } + creditApplication.page.hasEmployment = true; + return this.saveCreditApplication(); + } + + public saveOtherIncomeInformation(otherIncomes: Array, borrowerType: BorrowerType): void { + if (otherIncomes) { + const setIndividual = this.getIndividualByBorrowerType(borrowerType); + otherIncomes.forEach((eachIncome: IncomeInformation) => { + if (eachIncome) { + eachIncome.amount = MaskingService.unMaskDecimalNumber(eachIncome.amount); + } + }, this); + setIndividual.otherIncomeInformation = otherIncomes; + return; + } + } + + public saveEmploymentInformation(employmentHistory: Array, borrowerType: BorrowerType): void { + if (employmentHistory) { + const setIndividual = this.getIndividualByBorrowerType(borrowerType); + employmentHistory.forEach((eachEmployer: EmploymentInformation) => { + if (eachEmployer) { + eachEmployer.salary = MaskingService.unMaskDecimalNumber(eachEmployer.salary); + } + }, this); + setIndividual.employmentInformation = employmentHistory; + return; + } + } + + public saveHMDAInformation(formPage, borrowerType: BorrowerType): Observable { + const creditApplication = this.getFullCreditApplication(); + const setIndividual: Individual = this.getIndividualByBorrowerType(borrowerType); + setIndividual.genderInformation = formPage.genderInformation; + setIndividual.ethnicityInformation = formPage.ethnicityInformation; + setIndividual.raceInformation = formPage.raceInformation; + creditApplication.page.hasHMDA = true; + return this.saveCreditApplication(); + } + + public saveNoteInformation(noteData: any, creditApplication: CreditApplication) { + if (creditApplication.note) { + creditApplication.note += noteData.note; + } else { + creditApplication.note = noteData.note; + } + creditApplication.primaryContactMethod = noteData.primaryContact; + creditApplication.promoCode = noteData.promoCode; + creditApplication.page.hasNote = true; + return creditApplication; + } + + public saveAdditionalInformation(additionalInformation: AdditionalInformation, creditApplication: CreditApplication) { + creditApplication.modFeeAccount = additionalInformation.modFeeAccount; + return creditApplication; + } + + public saveSummaryInformation(additionalInformation: AdditionalInformation, noteData: any): Observable { + let creditApplication = this.getFullCreditApplication(); + creditApplication = this.saveNoteInformation(noteData, creditApplication); + creditApplication = this.saveAdditionalInformation(additionalInformation, creditApplication); + return this.saveCreditApplication(); + } + + public saveDisclosureInformation(disclosureInformation: DisclosureInformation): Observable { + const creditApplication = this.getFullCreditApplication(); + creditApplication.disclosureInformation = disclosureInformation; + creditApplication.disclosureInformation.date = new Date(); + creditApplication.page.hasDisclosure = true; + return this.saveCreditApplication(); + } + + public saveEquityMemberInformation(equityMemberInfo: any): Observable { + const creditApplication = this.getFullCreditApplication(); + + equityMemberInfo.limitRequested = MaskingService.unMaskNumber(equityMemberInfo.limitRequested); + creditApplication.purposeOfLoan = equityMemberInfo.purposeOfLoan ? + equityMemberInfo.purposeOfLoan : creditApplication.loanType.defaultPurpose; + if (equityMemberInfo.termOfLoan) { + creditApplication.termOfLoan = equityMemberInfo.termOfLoan; + } + creditApplication.clientMessage = equityMemberInfo.clientMessage; + creditApplication.agreementRequirement = equityMemberInfo.agreementRequirement; + return this.saveMemberInformation(equityMemberInfo); + } + + public saveMemberInformation(memberInfo: MembershipInformation): Observable { + const creditApplication = this.getFullCreditApplication(); + + memberInfo.limitRequested = MaskingService.unMaskNumber(memberInfo.limitRequested); + creditApplication.hasCoApplicant = memberInfo.applicantType; + creditApplication.limitRequested = memberInfo.limitRequested ? memberInfo.limitRequested : 0; + creditApplication.purposeOfLoanSignature = memberInfo.purposeOfLoanSignature; + creditApplication.confirmJointCredit = memberInfo.confirmJointCredit; + creditApplication.agreementRequirement = memberInfo.agreementRequirement; + if (memberInfo.livesInMaritalPropertyState === CreditApplicationValues.CHOICE_YES) { + creditApplication.fullInfoBorrowers[0].addressInformation.state = CreditApplicationValues.STATE_REQUIRED_COAPP; + } else if (memberInfo.livesInMaritalPropertyState === CreditApplicationValues.CHOICE_NO && + !creditApplication.fullInfoBorrowers[0].addressInformation.state) { + creditApplication.fullInfoBorrowers[0].addressInformation.state = CreditApplicationValues.UNDEFINED; + } + + if (creditApplication.hasCoApplicant === CreditApplicationValues.CHOICE_NO && + creditApplication.fullInfoBorrowers.length > 1) { + creditApplication.fullInfoBorrowers.pop(); + } + if (memberInfo.applicantType === CreditApplicationValues.CHOICE_YES && + memberInfo.coApplicantLivesInMaritalPropertyState === CreditApplicationValues.CHOICE_YES) { + creditApplication.coApplicantLivesInMaritalPropertyState = true; + if (creditApplication.fullInfoBorrowers.length === 2) { + creditApplication.fullInfoBorrowers[1].addressInformation.state = CreditApplicationValues.STATE_REQUIRED_COAPP; + } + } else if (memberInfo.applicantType === CreditApplicationValues.CHOICE_YES && + memberInfo.coApplicantLivesInMaritalPropertyState === CreditApplicationValues.CHOICE_NO) { + creditApplication.coApplicantLivesInMaritalPropertyState = false; + if (creditApplication.fullInfoBorrowers.length === 2) { + creditApplication.fullInfoBorrowers[1].addressInformation.state = CreditApplicationValues.UNDEFINED; + } + } + creditApplication.page.hasMemberInfo = true; + return this.saveCreditApplication(); + } + + public saveCollateralInformation(values: any): Observable { + const creditApplication = this.getFullCreditApplication(); + values.collateralInformation.estimatedValue = MaskingService.unMaskNumber(values.collateralInformation.estimatedValue); + if (values.collateralInformation.rentPaid && values.collateralInformation.onRentedLot === CreditApplicationValues.CHOICE_YES) { + values.collateralInformation.rentPaid = MaskingService.unMaskDecimalNumber(values.collateralInformation.rentPaid); + } else if (values.collateralInformation.rentPaid) { + delete values.collateralInformation.rentPaid; + } + + if (values.collateralInformation.annualPropertyTax && values.collateralInformation.onRentedLot === CreditApplicationValues.CHOICE_NO) { + values.collateralInformation.annualPropertyTax = MaskingService.unMaskDecimalNumber(values.collateralInformation.annualPropertyTax); + } else if (values.collateralInformation.annualPropertyTax) { + delete values.collateralInformation.annualPropertyTax; + } + + creditApplication.collateralInformation = values.collateralInformation; + if (this.isHelocRateMod(creditApplication.loanType.title)) { + creditApplication.collateralInformation.estimatedValue = this.defaultHELOCestimatedValue; + creditApplication.collateralInformation.propertyType = this.defaultHELOCpropertyType; + } + Object.assign(creditApplication.collateralInformation, values.addressInformation); + creditApplication.page.hasCollateralInfo = true; + return this.saveCreditApplication(); + } + + public saveVehicleCollateralInformation(fields: { + vehicleInformation: VehicleCollateralInformation, + boatAddressInformation?: AddressInformation}): Observable { + + const vehicleInfo = this.deleteUnusedVehicleFields(fields.vehicleInformation); + const addressInfo = fields.boatAddressInformation; + const creditApplication = this.getFullCreditApplication(); + + vehicleInfo.mileage = MaskingService.unMaskNumber(vehicleInfo.mileage); + vehicleInfo.value = MaskingService.unMaskNumber(vehicleInfo.value); + + if (!vehicleInfo.condition) { + vehicleInfo.condition = 'Used'; + } + + if (vehicleInfo.term) { + creditApplication.termOfLoan = vehicleInfo.term; + } + + if (creditApplication.loanType.title === LoanTitle.Boat) { + if (addressInfo) { + vehicleInfo.boatAddress = addressInfo; + } else { + vehicleInfo.boatAddress = creditApplication.fullInfoBorrowers[0].addressInformation; + } + } + + creditApplication.vehicleInformation = vehicleInfo; + creditApplication.page.hasVehicleCollateral = true; + + return this.saveCreditApplication(); + } + + public saveAddressAndResidenceInformation(fields: { + addressInfo?: AddressInformation, + priorAddressInfo?: Array, + mailingInfo?: AddressInformation, + residenceInfo?: ResidenceInformation, + priorResidenceInfo?: Array, + hasSameAddressAsSpouse?: string + }, borrowerType: BorrowerType): Observable { + const creditApplication = this.getFullCreditApplication(); + this.saveAllResidenceInformation(borrowerType, fields.residenceInfo, fields.priorResidenceInfo); + this.saveAllAddressInformation(borrowerType, fields.addressInfo, fields.mailingInfo, fields.priorAddressInfo); + + const setIndividual: Individual = this.getIndividualByBorrowerType(borrowerType); + if (fields.hasSameAddressAsSpouse && setIndividual) { + setIndividual.hasSameAddressAsSpouse = fields.hasSameAddressAsSpouse; + } + creditApplication.page.hasResidenceInfo = true; + return this.saveCreditApplication(); + } + + public saveAllAddressInformation( + borrowerType: BorrowerType, + addressInfo: AddressInformation, + mailingInfo?: AddressInformation, + priorAddressInfo?: Array): Observable { + const creditApplication = this.getFullCreditApplication(); + const setIndividual: Individual = this.getIndividualByBorrowerType(borrowerType); + if (!addressInfo || !setIndividual) { + return; + } + + setIndividual.addressInformation = addressInfo; + if (setIndividual.addressInformation.state !== CreditApplicationValues.STATE_REQUIRED_COAPP) { + setIndividual.spouseType = null; + } + + if (priorAddressInfo) { + setIndividual.priorAddressInformation = priorAddressInfo; + } + if (mailingInfo) { + setIndividual.mailingAddressInformation = mailingInfo; + } + creditApplication.page.hasAddress = true; + } + + public saveAllResidenceInformation( + borrowerType: BorrowerType, + residenceInfo: ResidenceInformation, + priorResidenceInfo?: Array): Observable { + + const setIndividual: Individual = this.getIndividualByBorrowerType(borrowerType); + if (!residenceInfo || !setIndividual) { + return; + } + + if (residenceInfo.residenceMonthlyPayment) { + residenceInfo.residenceMonthlyPayment = MaskingService.unMaskDecimalNumber(residenceInfo.residenceMonthlyPayment); + } + + setIndividual.residenceInformation = residenceInfo; + + if (priorResidenceInfo) { + priorResidenceInfo.forEach((eachResidenceInfo) => { + if (eachResidenceInfo.residenceMonthlyPayment) { + eachResidenceInfo.residenceMonthlyPayment = MaskingService.unMaskDecimalNumber(eachResidenceInfo.residenceMonthlyPayment); + } + }, this); + + setIndividual.priorResidenceInformation = priorResidenceInfo; + } + } + + public saveCreditApplication(): Observable { + const creditApplication = this.getFullCreditApplication(); + if (this.hasMinimumSaveInformation()) { + return this.creditApplicationDaoService.saveApplication(creditApplication) + .pipe( + map(result => { + if (result) { + creditApplication.loanId = result['id']; + creditApplication.appDetailId = result['appDetailId']; + creditApplication.error = result['error']; + return true; + } + return false; + }), + catchError((err, caught) => { + return of(false); + }) + ); + } else if(creditApplication.loanId == null) { + return this.creditApplicationDaoService.createEmptyApplication(creditApplication) + .pipe( + map(result => { + if (result) { + creditApplication.loanId = result['id']; + creditApplication.appDetailId = result['appDetailId']; + creditApplication.error = result['error']; + return true; + } + return false; + }), + catchError((err, caught) => { + return of(false); + }) + ); + } else { + return of(true); + } + } + + public hasMinimumSaveInformation(): boolean { + const creditApplication = this.getFullCreditApplication(); + const personalInformation = creditApplication.fullInfoBorrowers[0].personalInformation; + const addressInformation = creditApplication.fullInfoBorrowers[0].addressInformation; + if (personalInformation.firstName.length > 1 && addressInformation.addressLine1.length > 1) { + return true; + } else { + return false; + } + } + + public constructCreditApplicationByType(loanType: LoanType, parsedData?: any): CreditApplication { + const application = new CreditApplication(loanType); + if (parsedData) { + Object.assign(application, parsedData); + } + this.setDefaults(application); // Refactor here when different types have different defaults. + return application; + } + + public getLoanTypeForValue(loanTypeValue: string): LoanType { + let foundLoanType = null; + LoanTypeSet.forEach((loanType, index) => { + if (loanType.id === loanTypeValue) { + foundLoanType = loanType; + } + }, this); + + return foundLoanType; + } + + public isMarriedApplicant(): boolean { + const primary: Individual = this.getIndividualByBorrowerType(BorrowerType.PRIMARY_BORROWER); + const coborrower: Individual = this.getIndividualByBorrowerType(BorrowerType.CO_BORROWER); + if (primary + && primary.personalInformation.maritalStatus === CreditApplicationValues.CHOICE_YES + && primary.addressInformation.state === CreditApplicationValues.STATE_REQUIRED_COAPP) { + return true; + } else if (coborrower + && coborrower.personalInformation.maritalStatus === CreditApplicationValues.CHOICE_YES + && coborrower.addressInformation.state === CreditApplicationValues.STATE_REQUIRED_COAPP) { + return true; + } + return false; + } + + public canAcceptIndividualPrefill(): boolean { + const application = this.getFullCreditApplication(); + if (application && application.fullInfoBorrowers && application.fullInfoBorrowers.length > 0) { + const personal = application.fullInfoBorrowers[0].personalInformation; + if (personal && personal.lastName && personal.firstName && personal.email) { + return false; + } + } + return true; + } + + public generateDefaultDisclosure(): DisclosureInformation { + return { + contact: true, + truthful: false + }; + } + + public generateDefaultTattleSpousePersonalInformation(): TattleSpousePersonalInformation { + return { + firstName: '', + middleInitial: '', + lastName: '' + }; + } + + public static generateDefaultResidenceInformation(isHelocRateMod = false): ResidenceInformation { + return { + isMailingAddressSame: true, + residenceMonthlyPayment: null, + residenceTimeYears: isHelocRateMod ? '2' : '', + residenceTimeMonths: '0', + residenceStatus: '0' + }; + } + + public generateDefaultFullInfoBorrower(isHelocRateMod = false): Individual { + if (isHelocRateMod) { + return { + addressInformation: this.generateDefaultAddressInformation(), + residenceInformation: CreditApplicationService.generateDefaultResidenceInformation(isHelocRateMod), + personalInformation: this.generateDefaultPersonalInformation(), + ethnicityInformation: this.generateDefaultEthnicityInformation(isHelocRateMod), + genderInformation: this.generateDefaultGenderInformation(isHelocRateMod), + raceInformation: this.generateDefaultRaceInformation(isHelocRateMod), + employmentInformation: this.generateDefaultEmploymentForHelocRateModification() + }; + } + return { + addressInformation: this.generateDefaultAddressInformation(), + residenceInformation: CreditApplicationService.generateDefaultResidenceInformation(), + personalInformation: this.generateDefaultPersonalInformation() + }; + } + + public generateDefaultEmploymentForHelocRateModification(): Array { + return [{ + employerName: 'Other', + employmentType: '2', + jobTitle: 'Other', + salary: 10000, + schedule: FrequencyTypeEnum.Monthly.toString(), + employmentLengthYears: 2, + employmentLengthMonths: 0, + workPhoneNumber: null + }]; + } + + public generateDefaultTattleSpouse(): TattleIndividual { + return { + personalInformation: this.generateDefaultTattleSpousePersonalInformation(), + addressInformation: this.generateDefaultAddressInformation(), + hasSameAddressAsSpouse: 'yes' + }; + } + + public generateDefaultAddressInformation(): AddressInformation { + return { + addressLine1: '', + addressLine2: '', + city: '', + state: '', + zipcode: '', + }; + } + + public generateDefaultPersonalInformation(): PersonalInformation { + return { + firstName: '', + middleInitial: '', + lastName: '', + dateOfBirth: '', + socialSecurityNumber: null, + primaryPhoneNumberType: PhoneTypeEnum.Home, + primaryPhoneNumber: null, + secondaryPhoneNumberType: null, + secondaryPhoneNumber: null, + email: '' + }; + } + + public generateDefaultEthnicityInformation(notProvidedDefault = false): EthnicityInformation { + return { + hispanicOrLatino: false, + mexican: false, + puertoRican: false, + cuban: false, + otherHispanicLatino: false, + otherHispanicLatinoText: '', + notHispanicOrLatino: false, + notProvided: notProvidedDefault + }; + } + + public generateDefaultGenderInformation(notProvidedDefault = false): GenderInformation { + return { + female: false, + male: false, + notProvided: notProvidedDefault + }; + } + + public generateDefaultRaceInformation(notProvidedDefault = false): RaceInformation { + return { + americanIndianAlaskaNative: false, + americanIndianAlaskaNativeText: '', + blackOrAfricanAmerican: false, + asian: false, + asianIndian: false, + chinese: false, + filipino: false, + japanese: false, + korean: false, + vietnamese: false, + otherAsian: false, + otherAsianText: '', + hawaiian: false, + nativeHawaiian: false, + guamanianChamorro: false, + samoan: false, + otherPacificIslander: false, + otherPacificIslanderText: '', + white: false, + notProvided: notProvidedDefault, + }; + } + + public generateDefaultPage(helocRateMod = false): ApiPage { + return { + hasAddress: false, + hasEmployment: helocRateMod, + hasPersonal: false, + hasMemberInfo: false, + hasSpouseInfo: false, + hasTattleSpouseInfo: false, + hasResidenceInfo: false, + hasVehicleCollateral: false, + hasHMDA: helocRateMod, + hasDisclosure: false, + hasCollateralInfo: false, + hasNote: helocRateMod + }; + } + + public generateDefaultError(): ApiError { + return { + code: ApiErrorCode.No_Error, + message: 'No Errors' + }; + } + + public generateSpecialNote(title: string): string { + return `Custom Product-${title} `; + } + + public deleteUnusedVehicleFields(vehicleInfo: VehicleCollateralInformation): VehicleCollateralInformation { + + if (vehicleInfo.loanPurpose !== 'Purchase') { + delete vehicleInfo.transferring; + delete vehicleInfo.licensePlateNumber; + delete vehicleInfo.dealerName; + delete vehicleInfo.dealerPhone; + delete vehicleInfo.seller; + delete vehicleInfo.titleBranded; + delete vehicleInfo.lien; + vehicleInfo.boatTitleFiles = []; + vehicleInfo.trailerTitleFiles = []; + vehicleInfo.purchaseContractFiles = []; + } + + if (vehicleInfo.transferring === 'no') { + delete vehicleInfo.licensePlateNumber; + } + + if (vehicleInfo.seller === 'Private_Party') { + delete vehicleInfo.dealerName; + delete vehicleInfo.dealerPhone; + } + + return vehicleInfo; + } + + public isHelocRateMod(title: string): boolean { + return title === LoanTitle.HELOC_RateMod; + } + + private setDefaults(creditApplication: CreditApplication) { + // for now, set these defaults, when they are missing + // this may need to be refactored later on, but since there's only 3 defaults set we can check each one + if (!creditApplication.disclosureInformation) { + Object.assign(creditApplication, {disclosureInformation: this.generateDefaultDisclosure()}); + } + + const isHelocRateModApp = this.isHelocRateMod(creditApplication.loanType.title); + + if (creditApplication && isHelocRateModApp) { + Object.assign(creditApplication, {limitRequested: 5000}); + Object.assign(creditApplication, {purposeOfLoan: LoanPurpose.Other}); + Object.assign(creditApplication, {termOfLoan: 60}); + } + + if (creditApplication.fullInfoBorrowers) { + // we need to check each part of the full info borrower now.... + if (!creditApplication.fullInfoBorrowers[0].addressInformation) { + Object.assign(creditApplication.fullInfoBorrowers[0], + {addressInformation: this.generateDefaultAddressInformation()}); + } + if (!creditApplication.fullInfoBorrowers[0].residenceInformation) { + Object.assign(creditApplication.fullInfoBorrowers[0], + {residenceInformation: CreditApplicationService.generateDefaultResidenceInformation()}); + } + if (!creditApplication.fullInfoBorrowers[0].personalInformation) { + Object.assign(creditApplication.fullInfoBorrowers[0], + {personalInformation: this.generateDefaultPersonalInformation()}); + } + } + if (!creditApplication.fullInfoBorrowers) { + Object.assign(creditApplication, {fullInfoBorrowers: [this.generateDefaultFullInfoBorrower(isHelocRateModApp)]}); + } + if (!creditApplication.note && isHelocRateModApp) { + Object.assign(creditApplication, {note: this.generateSpecialNote(LoanTitle.HELOC_RateMod)}); + } + if (!creditApplication.status) { + Object.assign(creditApplication, {status: ApiStatus.IN_PROGRESS}); + } + if (!creditApplication.error) { + Object.assign(creditApplication, {error: this.generateDefaultError()}); + } + if (!creditApplication.page) { + Object.assign(creditApplication, {page: this.generateDefaultPage(isHelocRateModApp)}); + } + return creditApplication; + } + + /** + * Clear an application after sending. This may mature; for now we keep only the + * loan type, loan id, and decision. + */ + public clearApplication(): CreditApplication { + const creditApplication = this.getFullCreditApplication(); + if (creditApplication) { + const application = new CreditApplication(creditApplication.loanType); + Object.assign(application, creditApplication.decision, creditApplication.loanId, creditApplication.hasBeenCompleted); + return application; + } + return creditApplication; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.ts new file mode 100644 index 0000000..a8b6d73 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/credit-application.ts @@ -0,0 +1,95 @@ +import { VehicleCollateralInformation } from '../loan-wizard/field-groups/vehicle/vehicle-collateral-information'; +import { AddressInformation } from '../loan-wizard/field-groups/address/address-information'; +import { PersonalInformation } from '../loan-wizard/field-groups/personal/personal-information'; +import { EmploymentInformation } from '../loan-wizard/field-groups/employment/employment-information'; +import { DisclosureInformation } from '../loan-wizard/field-groups/disclosures/credit-card-disclosure'; +import { LoanType } from './loan-type'; +import { ResidenceInformation } from '../loan-wizard/field-groups/address/residence-information'; +import { EthnicityInformation } from '../loan-wizard/field-groups/hmda/ethnicity-information'; +import { GenderInformation } from '../loan-wizard/field-groups/hmda/gender-information'; +import { RaceInformation } from '../loan-wizard/field-groups/hmda/race-information'; +import { CollateralInformation } from '../loan-wizard/field-groups/equity/collateral-information'; +import { IsAddressSameInformation } from '../loan-wizard/field-groups/address/is-address-same-information'; +import { IncomeInformation } from '../loan-wizard/field-groups/employment/income-information'; +import { AlimonyInformation } from '../loan-wizard/field-groups/employment/alimony-information'; +import { TattleSpousePersonalInformation } from '../loan-wizard/field-groups/tattle-spouse/tattle-spouse-personal-information'; +import { LoanPurpose } from './loan-type/loan-purpose'; +import { ApiPage } from './api-models/api-page'; +import { ApiStatus } from './api-models/api-status'; +import { ApiError } from './api-models/api-error'; +import { DecisionStatus } from './decision/decision-status'; + +export interface CollateralInformationGroup extends CollateralInformation, AddressInformation, IsAddressSameInformation, LienPosition {} + +export enum SpouseType { + 'FullInfoSpouse' = 'fullInfoSpouse', + 'TattleSpouse' = 'tattleSpouse' +} + +export class Individual { + public personalInformation: PersonalInformation; + public mailingAddressInformation?: AddressInformation; + public addressInformation: AddressInformation; + public priorAddressInformation?: Array; + public residenceInformation: ResidenceInformation; + public priorResidenceInformation?: Array; + public employmentInformation?: Array; + public otherIncomeInformation?: Array; + public alimonyInformation?: AlimonyInformation; + public ethnicityInformation?: EthnicityInformation; + public genderInformation?: GenderInformation; + public raceInformation?: RaceInformation; + public spouseType?: SpouseType; + public fullInfoSpouse?: Individual; + public tattleSpouse?: TattleIndividual; + public hasSameAddressAsSpouse?: string; +} + +export class TattleIndividual { + public personalInformation: TattleSpousePersonalInformation; + public addressInformation?: AddressInformation; + public hasSameAddressAsSpouse?: string; +} + +export class CreditApplication { + public loanId?: string; + public limitRequested: number; + public appDetailId?: string; + public loanType: LoanType; + public decision?: DecisionStatus; + public vehicleInformation?: VehicleCollateralInformation; + public disclosureInformation?: DisclosureInformation; + public collateralInformation?: CollateralInformationGroup; + public lienPosition?: LienPosition; + public hasCoApplicant: string; + public confirmJointCredit?: string; + public hasCoApplicantSpouse: string; + public coApplicantLivesInMaritalPropertyState?: boolean; + public hasBeenCompleted: boolean; + public fullInfoBorrowers: Array; + public purposeOfLoan?: LoanPurpose; + public purposeOfLoanSignature?: string; + public termOfLoan?: string; + public note?: string; + public primaryContactMethod: string; + public promoCode?: string; + public page: ApiPage; + public status: ApiStatus; + public error: ApiError; + public continueLoan?: boolean; + public clientMessage?: string; + public modFeeAccount?: string; + public agreementRequirement?: boolean; + + constructor(loanType: LoanType) { + this.loanType = loanType; + this.hasBeenCompleted = false; + } +} + +export class LienPosition { + public hasFirstMortgage: string; + public hasSecondMortgage: string; + public payoffFirstMortgage: boolean; + public payoffSecondMortgage: boolean; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/data-table.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/data-table.service.spec.ts new file mode 100644 index 0000000..743ced4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/data-table.service.spec.ts @@ -0,0 +1,53 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { DataTableService } from './data-table.service'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Data Table Service', () => { + + let service: DataTableService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot() + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [ + + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(DataTableService); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should determine that an applicant is a Landmark Credit Union employee', () => { + expect(service.isLandmarkEmployee('landmark')).toBe(true); + expect(service.isLandmarkEmployee('LMCU')).toBe(true); + expect(service.isLandmarkEmployee('LCU')).toBe(true); + expect(service.isLandmarkEmployee('Landmark CU')).toBe(true); + expect(service.isLandmarkEmployee('Landmark Credit')).toBe(true); + expect(service.isLandmarkEmployee('Landmark Credit Union')).toBe(true); + expect(service.isLandmarkEmployee('LMCU')).toBe(true); + expect(service.isLandmarkEmployee('Landmark ')).toBe(true); + expect(service.isLandmarkEmployee('Landmark Banking Services')).toBe(true); + expect(service.isLandmarkEmployee('LandmarkCreditUnion')).toBe(true); + expect(service.isLandmarkEmployee('LandMark')).toBe(true); + + expect(service.isLandmarkEmployee('Blandmark')).toBe(false); + expect(service.isLandmarkEmployee('GLMCU')).toBe(false); + expect(service.isLandmarkEmployee('Landmarc')).toBe(false); + expect(service.isLandmarkEmployee('CLCU')).toBe(false); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/data-table.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/data-table.service.ts new file mode 100644 index 0000000..e929f3c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/data-table.service.ts @@ -0,0 +1,97 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class DataTableService { + constructor() { } + + private stateList: {[abbrev: string]: string} = { + 'AA' : 'Armed Forces Americas', + 'AE' : 'Armed Forces Europe', + 'AL' : 'Alabama', + 'AK' : 'Alaska', + 'AP' : 'Armed Forces Pacific', + 'AS' : 'American Samoa', + 'AZ' : 'Arizona', + 'AR' : 'Arkansas', + 'CA' : 'California', + 'CO' : 'Colorado', + 'CT' : 'Connecticut', + 'DE' : 'Delaware', + 'DC' : 'District Of Columbia', + 'FM' : 'Federated States Of Micronesia', + 'FL' : 'Florida', + 'GA' : 'Georgia', + 'GU' : 'Guam', + 'HI' : 'Hawaii', + 'ID' : 'Idaho', + 'IL' : 'Illinois', + 'IN' : 'Indiana', + 'IA' : 'Iowa', + 'KS' : 'Kansas', + 'KY' : 'Kentucky', + 'LA' : 'Louisiana', + 'ME' : 'Maine', + 'MH' : 'Marshall Islands', + 'MD' : 'Maryland', + 'MA' : 'Massachusetts', + 'MI' : 'Michigan', + 'MN' : 'Minnesota', + 'MS' : 'Mississippi', + 'MO' : 'Missouri', + 'MT' : 'Montana', + 'NE' : 'Nebraska', + 'NV' : 'Nevada', + 'NH' : 'New Hampshire', + 'NJ' : 'New Jersey', + 'NM' : 'New Mexico', + 'NY' : 'New York', + 'NC' : 'North Carolina', + 'ND' : 'North Dakota', + 'MP' : 'Northern Mariana Islands', + 'OH' : 'Ohio', + 'OK' : 'Oklahoma', + 'OR' : 'Oregon', + 'PW' : 'Palau', + 'PA' : 'Pennsylvania', + 'PR' : 'Puerto Rico', + 'RI' : 'Rhode Island', + 'SC' : 'South Carolina', + 'SD' : 'South Dakota', + 'TN' : 'Tennessee', + 'TX' : 'Texas', + 'UT' : 'Utah', + 'VT' : 'Vermont', + 'VI' : 'Virgin Islands', + 'VA' : 'Virginia', + 'WA' : 'Washington', + 'WV' : 'West Virginia', + 'WI' : 'Wisconsin', + 'WY' : 'Wyoming' + }; + + private landmarkEmployerNameList = [ + 'LCU', + 'LANDMARK', + 'LANDMARK CREDIT', + 'LANDMARK CREDIT UNION', + 'LANDMARK CU', + 'LMCU' + ]; + + public getStateList(): {[abbrev: string]: string} { + return this.stateList; + } + + public isLandmarkEmployee(employerName: string): boolean { + employerName = employerName.toUpperCase(); + employerName.trim(); + for (let i = 0; i < this.landmarkEmployerNameList.length; i++) { + if (employerName.indexOf(this.landmarkEmployerNameList[i]) === 0) { + return true; + } + } + return false; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/date/date-builder.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/date/date-builder.ts new file mode 100644 index 0000000..1175464 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/date/date-builder.ts @@ -0,0 +1,44 @@ + + +export class DateBuilder { + public static buildPastDate(days: number): Date { + const date = new Date(); + date.setDate(date.getDate() - days); + return date; + } + + public static buildFutureDate(days: number): Date { + const date = new Date(); + date.setDate(date.getDate() + days); + return date; + } + + public static formatDateAsString(date: Date): string { + const month = date.getMonth() + 1; + const day = date.getDate(); + const year = date.getFullYear(); + + const formattedMonth = (month < 10) ? ('0' + month) : month; + const formattedDay = (day < 10) ? ('0' + day) : day; + + return formattedMonth + '/' + formattedDay + '/' + year; + } + + public static formatStringAsDate(date: string): Date { + const dateObj: Date = new Date(); + dateObj.setDate(parseInt(date.substring(3, 5), 10)); + dateObj.setMonth(parseInt(date.substring(0, 2), 10) - 1); + dateObj.setFullYear(parseInt(date.substring(6, 10), 10)); + return dateObj; + } + + public static setBeginOfDay (date: Date): Date { + date.setHours(0, 0, 0, 0); + return date; + } + + public static setEndOfDay (date: Date): Date { + date.setHours(23, 59, 59, 999); + return date; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.component.html new file mode 100644 index 0000000..83536ac --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.component.html @@ -0,0 +1,186 @@ +
+

Dealer Portal

+ For password resets, please contact Indirect Lending at Landmark Credit Union: 262.796.4511 +
+
+
+
+ +
+
+
+ +
+ +
+ +
+ + +
+
+ + +
+
+ +
+
+
+ +
+ +
+ +
+ + +
+
+ +
+
+ +
+
+
+ +
+ +
+ +
+ + +
+
+ + +
+
+ +
+
+
+ +
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + +
+
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+ + +
+
+
+
+
+ +
+
+
+

+ No matching application record found. Please verify the information entered and try again. Click here to reset the form. +

+
+
+
+ +
+
+
+
+
+ + Transmitting Documents: {{ localState.uploadedFileCount }} of {{ localState.totalFileCount }} +
+
+
+
+
+ +
+
+
+

+ Documents successfully uploaded to Application ID {{ localState.applicationId }}. +

+
+
+
+ +
+
+
+

+ We are unable to complete your request at this time, please try again later. +

+

+ If the problem continues, please contact Indirect Lending at Landmark Credit Union: 262.796.4511 +

+
+
+
+ +
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.component.ts new file mode 100644 index 0000000..d72d8b5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.component.ts @@ -0,0 +1,207 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { FormGroup } from '@angular/forms'; +import { Subscription } from 'rxjs'; + +import { DealershipPortalPageService } from './dealership-portal-page.service'; +import { CreditApplication } from 'src/app/shared/credit-application'; +import { LoanTypeSet } from '../loan-type'; +import { LoanTitle } from '../loan-type/loan-title'; + +@Component({ + selector: 'lw-dealership-portal-page', + templateUrl: './dealership-portal-page.component.html' +}) +export class DealershipPortalComponent implements OnInit, OnDestroy { + protected creditApplication: CreditApplication; + public pageForm: FormGroup; + private statusSubscription: Subscription; + + private readonly FORM_STATE_INVALID = 'INVALID'; + + public localState = { + nextDisabled: true, + previousDisabled: false, + applicationId: null, + cosmosId: null, + enableUploadDocumentsButton: false, + showUploadDocumentsButtonSpinner: false, + showUploadForm: false, + files: [], + isUploading: false, + canTransmit: false, + isTransmitting: false, + transmitSuccess: null, + uploadedFileCount: 0, + totalFileCount: 0 + }; + + constructor( + private dealershipPortalPageService: DealershipPortalPageService, + private route: ActivatedRoute) {} + + public ngOnInit() { + this.initializeFormGroups(); + this.updateLocalState(this.pageForm.value); + + (document.querySelector('lw-header b') as HTMLElement).innerText = 'Call 262.796.4511'; + (document.querySelector('lw-header nav div a') as HTMLElement).setAttribute('href', 'tel:+1-262-796-4511') + + this.creditApplication = new CreditApplication(LoanTypeSet[7]); + this.creditApplication.loanType.title = LoanTitle.Vehicle; + } + + public initializeFormGroups() { + this.pageForm = this.dealershipPortalPageService.createPageGroup(); + + this.statusSubscription = this.pageForm.statusChanges.subscribe((newStatus) => { + this.localState.cosmosId = null; + if(this.pageForm.get('dealershipCode').valid && + this.pageForm.get('dealershipPassword').valid && + this.pageForm.get('applicationId').valid && + this.pageForm.get('applicantLastName').valid) { + this.localState.enableUploadDocumentsButton = true; + } else { + this.localState.enableUploadDocumentsButton = false; + } + }); + } + + ngOnDestroy(): void { + if(this.statusSubscription) { + this.statusSubscription.unsubscribe(); + } + } + + public authenticateDealership() { + this.localState.showUploadForm = null; + this.localState.showUploadDocumentsButtonSpinner = true; + + this.disableInputs(); + + this.localState.applicationId = this.pageForm.get('applicationId').value; + this.dealershipPortalPageService.authenticate(this.pageForm.value).toPromise().then(result => { + this.localState.showUploadDocumentsButtonSpinner = false; + if (result['cosmosId'] != null) { + this.localState.cosmosId = result['cosmosId']; + this.localState.showUploadForm = true; + } else { + this.enableInputs(); + this.localState.showUploadForm = false; + this.localState.cosmosId = 'error'; + } + }).catch(error => { + this.localState.showUploadDocumentsButtonSpinner = false; + this.localState.cosmosId = 'error'; + this.localState.showUploadForm = false; + this.enableInputs(); + }); + } + + public enableInputs() { + let elements = document.querySelectorAll("input"); + for(let i = 0; i < elements.length; i++) { + elements[i].disabled = false; + } + } + + public disableInputs() { + let elements = document.querySelectorAll("input"); + for(let i = 0; i < elements.length; i++) { + elements[i].disabled = true; + } + } + + public resetForm() { + this.enableInputs(); + + this.pageForm.get('dealershipPassword').setValue(""); + this.pageForm.get('dealershipPassword').markAsPristine(); + this.pageForm.get('dealershipPassword').markAsUntouched(); + + this.pageForm.get('applicationId').setValue(""); + this.pageForm.get('applicationId').markAsPristine(); + this.pageForm.get('applicationId').markAsUntouched(); + + + this.pageForm.get('applicantLastName').setValue("") + this.pageForm.get('applicantLastName').markAsPristine(); + this.pageForm.get('applicantLastName').markAsUntouched(); + + if(this.pageForm.get('dealershipCode').invalid) { + (document.querySelector('#dealershipCode') as HTMLElement).focus(); + this.pageForm.get('dealershipCode').markAsPristine(); + this.pageForm.get('dealershipCode').setValue(""); + this.pageForm.get('dealershipCode').markAsUntouched(); + } else { + (document.querySelector('#dealershipPassword') as HTMLElement).focus(); + } + + this.localState.showUploadForm = false; + this.localState.canTransmit = false; + this.localState.transmitSuccess = null; + this.localState.isTransmitting = false; + this.localState.files = []; + this.localState.applicationId = null; + this.localState.cosmosId = null; + this.localState.enableUploadDocumentsButton = false; + this.localState.showUploadDocumentsButtonSpinner = false; + } + + public updateLocalState(nextValues: any) { + this.localState = { + nextDisabled: this.localState.nextDisabled, + previousDisabled: false, + applicationId: null, + cosmosId: null, + enableUploadDocumentsButton: false, + showUploadDocumentsButtonSpinner: false, + showUploadForm: false, + files: [], + isUploading: false, + isTransmitting: false, + canTransmit: false, + transmitSuccess: null, + uploadedFileCount: 0, + totalFileCount: 0 + }; + } + + public handleFiles(files) { + // If the file lengths are different, then make transmitSuccess null so that the error message is hidden if files are changed. + if(this.localState.files.length != files.length) { + this.localState.transmitSuccess = null; + } + this.localState.files = files; + this.localState.canTransmit = this.localState.files.filter(file => file.valid == false).length == 0; + } + + public handleUploadingFiles(isUploading) { + this.localState.isUploading = isUploading; + } + + public async transmitFiles() { + let transmissionError = false; + this.localState.uploadedFileCount = 1 + this.localState.totalFileCount = this.localState.files.length + while(this.localState.files.length > 0) { + this.localState.isTransmitting = true; + await this.dealershipPortalPageService.submitFiles(this.localState.cosmosId, this.pageForm.value).toPromise().then(result => { + this.localState.isTransmitting = false; + this.localState.files = result.files; + this.localState.uploadedFileCount += 1 + }).catch(error => { + this.localState.isTransmitting = false; + this.localState.transmitSuccess = false; + transmissionError = true; + }); + } + + if(transmissionError == false) { + this.localState.isTransmitting = false; + this.localState.transmitSuccess = true; + } + + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.service.ts new file mode 100644 index 0000000..d415855 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/dealership-portal/dealership-portal-page.service.ts @@ -0,0 +1,103 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { CommonGroups } from 'src/app/loan-wizard/field-groups/common-groups' +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; +import { catchError, map } from 'rxjs/operators'; +import { CreditApplication } from '../credit-application'; +import { ApiLoanContainer } from '../api-models/api-loan-container'; +import { ApiTranslatorService } from '../api-translator/api-translator.service'; +// import { PageService } from 'src/app/loan-wizard/'; + +@Injectable({ + providedIn: 'root' +}) +export class DealershipPortalPageService { + + public static readonly FORM_NAME = 'dealershipPortal'; + + constructor( + private router: Router, + private formBuilder: FormBuilder, + private httpClient: HttpClient, + private apiTranslatorService: ApiTranslatorService) { } + + public createPageGroup(): FormGroup { + const group = this.formBuilder.group({}); + + CommonGroups.DEALERSHIP_PORTAL.fields.forEach((eachField) => { + group.addControl(eachField.field, this.formBuilder.control('', eachField.validation.slice())); + }, this); + + return group; + } + + public buildStorageUrl(): string { + return environment.urlBase + '/Storage'; + } + + public buildLoanApplicationUrl(): string { + return environment.urlBase + '/LoanApplication'; + } + + public getApplication(applicationId: string) { + let observable: Observable; + observable = this.httpClient.post(this.buildStorageUrl() + "/DealershipPortal/" + applicationId, null, {withCredentials: true}) + .pipe(map(result => { + return result; + }), + catchError(err => { + if (err.status === 500) { + throw new Error('500: Server could not process API response'); + } else { + throw new Error('Bad request'); + } + }) + ); + + return observable; + } + + public authenticate(form: any) { + let observable: Observable; + observable = this.httpClient.post(this.buildLoanApplicationUrl() + "/AuthenticateDealership/", form, {withCredentials: true}) + .pipe(map(result => { + return result; + }), + catchError(err => { + if (err.status === 500) { + throw new Error('500: Server could not process API response'); + } else { + throw new Error('Bad request'); + } + }) + ); + + return observable; + } + + public submitFiles(cosmosId: string, form: any) { + let observable: Observable; + observable = this.httpClient.post(this.buildLoanApplicationUrl() + "/SubmitDealershipFiles/" + cosmosId, form, {withCredentials: true}) + .pipe(map(result => { + return result; + }), + catchError(err => { + if (err.status === 500) { + throw new Error('500: Server could not process API response'); + } else { + throw new Error('Bad request'); + } + }) + ); + + return observable; + } + + + public commandBuildApiLoanContainer(application: CreditApplication): ApiLoanContainer { + return this.apiTranslatorService.translateToApiLoanContainer(application); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision-status.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision-status.ts new file mode 100644 index 0000000..675ba8b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision-status.ts @@ -0,0 +1,26 @@ +export interface Stipulation { + id: number; + description: string; +} + +export enum DecisionState { + 'NotStarted' = 'NotStarted', + 'Requested' = 'Requested', + 'Ready' = 'Ready', + 'ErrorSubmit' = 'ErrorSubmit', + 'ErrorDecide' = 'ErrorDecide' +} + +export enum DecisionResult { + 'DecisionStatus_None' = 'DecisionStatus_None', + 'DecisionStatus_Approved' = 'DecisionStatus_Approved', + 'DecisionStatus_Counteroffer' = 'DecisionStatus_Counteroffer', + 'DecisionStatus_Review' = 'DecisionStatus_Review', + 'DecisionStatus_Declined' = 'DecisionStatus_Declined' +} + +export interface DecisionStatus { + state: DecisionState; + result: DecisionResult; + stipulations: Array; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision.service.spec.ts new file mode 100644 index 0000000..c339003 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision.service.spec.ts @@ -0,0 +1,68 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { DecisionService } from './decision.service'; +import { DecisionState } from './decision-status'; + +describe('DecisionService', () => { + + let service: DecisionService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + TranslateModule.forRoot() + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [] + }); + }); + + beforeEach(() => { + service = TestBed.get(DecisionService); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should create empty decision result', () => { + spyOn(service, 'createEmptyDecisionResult').and.callThrough(); + spyOn(service, 'createDecisionResult').and.callThrough(); + const emptyDecisionResult = service.createEmptyDecisionResult(true); + expect(service.createEmptyDecisionResult).toHaveBeenCalled(); + expect(service.createDecisionResult).toHaveBeenCalled(); + expect(emptyDecisionResult.state).toBe(DecisionState.NotStarted); + }); + + it('should create error decision result', () => { + spyOn(service, 'createErrorDecisionResult').and.callThrough(); + spyOn(service, 'createDecisionResult').and.callThrough(); + const errorDecision = service.createErrorDecisionResult(true, DecisionState.ErrorSubmit); + expect(service.createErrorDecisionResult).toHaveBeenCalled(); + expect(errorDecision.state).toBe(DecisionState.ErrorSubmit); + }); + + it('should lookup stipulation', () => { + spyOn(service, 'lookupStipulation').and.callThrough(); + let stipulation = service.lookupStipulation(20); + expect(service.lookupStipulation).toHaveBeenCalled(); + expect(stipulation).toEqual(service.stipulationMap[20]); + stipulation = service.lookupStipulation(58); + expect(service.lookupStipulation).toHaveBeenCalled(); + expect(stipulation).toEqual(service.stipulationMap[58]); + service.lookupStipulation(-2); + expect(service.lookupStipulation).toHaveBeenCalled(); + expect(service.stipulationMap[-2]).toBeFalsy(); + expect(service.lookupStipulation).toThrowError(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision.service.ts new file mode 100644 index 0000000..dafeded --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/decision/decision.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@angular/core'; +import { DecisionResult, DecisionState, DecisionStatus, Stipulation } from './decision-status'; + +@Injectable({ + providedIn: 'root' +}) +export class DecisionService { + + constructor() { } + + public readonly DEFAULT_STIPULATION = 'Additional stipulations apply and will be provided by your lender.'; + + public readonly stipulationMap = { + 20: 'A Personal Guaranty form is required for all business loans', + 58: 'Final Approval is dependent on Appraisal, Title, and Flood', + 59: 'Two years of income verification is required', + 82: 'Landmark Business savings account must be established', + 85: 'Must show proof borrower is a current student', + 92: 'Cards can only be issued to business owner and owner\'s spouse' + }; + public readonly alwaysStipulations = [ + 'All applications are subject to final review and verification by our underwriters' + ]; + + public readonly alwaysNoDecisionStipulations = [ + 'You\'ll be contacted by a loan officer' + ]; + + public createEmptyDecisionResult(shouldDecision: boolean): DecisionStatus { + const emptyDecision = {result: DecisionResult.DecisionStatus_Review, state: DecisionState.NotStarted, stipulations: []}; + return this.createDecisionResult(emptyDecision, shouldDecision); + } + + public createErrorDecisionResult(shouldDecision: boolean, + decisionState: DecisionState): DecisionStatus { + const emptyDecision = {result: '', state: decisionState, stipulations: []}; + return this.createDecisionResult(emptyDecision, shouldDecision); + } + + public createDecisionResult(response: any, shouldDecision: boolean = true): DecisionStatus { + const result = response; + const stipulations: Array = []; + if (!shouldDecision) { + this.alwaysNoDecisionStipulations.forEach((stipulation: string) => { + stipulations.push({ + id: -1, + description: stipulation + }); + }); + } + this.alwaysStipulations.forEach((stipulation: string) => { + stipulations.push({ + id: -1, + description: stipulation + }); + }); + response.stipulations.forEach((stipulation: number) => { + stipulations.push({ + id: stipulation, + description: this.lookupStipulation(stipulation) + }); + }, this); + result.stipulations = stipulations; + return result; + } + + public lookupStipulation(stipulationId: number): string { + return this.stipulationMap[stipulationId] || this.DEFAULT_STIPULATION; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/export/export.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/export/export.service.spec.ts new file mode 100644 index 0000000..83960fb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/export/export.service.spec.ts @@ -0,0 +1,97 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { ApiLoanContainer } from 'src/app/shared/api-models/api-loan-container'; +import { TestApiLoanContainer } from 'src/app/testing/shared-mocks/test-api-loan-container'; +import { ExportService } from './export.service'; +import { BranchData } from '../configuration/ui-settings'; +import { BranchConfig } from '../../admin/branch-config/branch-config'; + + +describe('Export Service', () => { + + let service: ExportService; + const loanContainer: ApiLoanContainer = TestApiLoanContainer; + const loanContainers: ApiLoanContainer[] = [loanContainer]; + const formattedDataSource = [{ + 'Date': loanContainers[0].date, + 'Branch': 'milwaukee', + 'Status': 'In_Progress', + 'Member #': '1231', + 'Individual #': '1231', + 'Loan Id #': '12341234', + 'Loan Type': 'CC_CashBack', + 'First Name': 'john', + 'Last Name': 'sanders', + 'Email Address': 'f@f.com', + 'Phone Number': '2622221215', + 'Secondary Number': undefined, + 'Work Number': '' + }]; + const branchConfig: BranchConfig = { + id: '5', + date: new Date(), + branchCodesByZip: [ + {zipcode: '53027', channelId: 123456789}, + {zipcode: '53111', channelId: 987654321} + ] + }; + const formattedBranchCodes = + [ + {'ZIP': '53027', 'ChannelId': 123456789}, + {'ZIP': '53111', 'ChannelId': 987654321} + ] + ; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [ + ExportService + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(ExportService); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should format a loan data source', () => { + const formattedData = service.formatLoanContainer(loanContainers); + expect(formattedData).toEqual(formattedDataSource); + }); + + it('should format a branch data source', () => { + const formattedData = service.formatBranchContainer(branchConfig.branchCodesByZip); + expect(formattedData).toEqual(formattedBranchCodes); + }); + + it('should construct a CSV from branch data', () => { + const formattedData = service.formatBranchContainer(branchConfig.branchCodesByZip); + const csvData = service.constructCSVData(formattedData, true); + expect(csvData).toEqual('ZIP,ChannelId\r\n53027,123456789\r\n53111,987654321'); + }); + + it('should construct a CSV from branch data, with stringify', () => { + const formattedData = service.formatBranchContainer(branchConfig.branchCodesByZip); + const csvData = service.constructCSVData(formattedData, false); + expect(csvData).toEqual('ZIP,ChannelId\r\n"53027",123456789\r\n"53111",987654321'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/export/export.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/export/export.service.ts new file mode 100644 index 0000000..e7a84fe --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/export/export.service.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@angular/core'; +import { ApiLoanContainer } from '../api-models/api-loan-container'; +import { saveAs } from 'file-saver'; +import { BranchConfig } from '../../admin/branch-config/branch-config'; + +@Injectable({ + providedIn: 'root' +}) +export class ExportService { + + public buildCSV(data: any, fileName: string, skipStringify?: boolean) { + const csvArray = this.constructCSVData(data, skipStringify); + const blob = new Blob([csvArray], {type: 'text/csv'}); + saveAs(blob, fileName + '.csv'); + } + + public constructCSVData(data: any, skipStringify: boolean): string { + const replacer = (key, value) => value === null ? '' : value; + const header = Object.keys(data[0]); + let csv = []; + if (skipStringify === true) { + csv = data.map(row => header.map(fieldName => row[fieldName]).join(',')); + } else { + csv = data.map(row => header.map(fieldName => JSON.stringify(row[fieldName], replacer)).join(',')); + } + csv.unshift(header.join(',')); + const csvArray = csv.join('\r\n'); + return csvArray; + } + + public formatLoanContainer(dataSource: ApiLoanContainer[]): any { + return dataSource.map(item => { + const container = {}; + container['Date'] = item.date; + container['Branch'] = item.branch; + container['Status'] = item.status; + container['Member #'] = item.memberId; + container['Individual #'] = item.individualId; + container['Loan Id #'] = item.loanId; + container['Loan Type'] = item.loanType; + container['First Name'] = item.firstName; + container['Last Name'] = item.lastName; + container['Email Address'] = item.loanApplication === null ? '' : item.loanApplication.primaryBorrower.email; + container['Phone Number'] = item.loanApplication === null ? '' : item.loanApplication.primaryBorrower.phone; + container['Secondary Number'] = item.loanApplication === null ? '' : item.loanApplication.primaryBorrower.secondaryPhone; + container['Work Number'] = item.loanApplication === null ? '' + : item.loanApplication.primaryBorrower.employers === null ? '' : item.loanApplication.primaryBorrower.employers[0].workPhone; + return container; + }); + } + + public exportLoanContainerAsCSV(dataSource: ApiLoanContainer[]) { + if (dataSource.length > 0) { + this.buildCSV(this.formatLoanContainer(dataSource), 'loan'); + } + } + + public exportBranchConfigAsCSV(dataSource: Array) { + if (dataSource.length > 0) { + this.buildCSV(this.formatBranchContainer(dataSource), 'branch_mapping', true); + } + } + + public formatBranchContainer(dataSource: any[]): any { + return dataSource.map(item => { + const container = {}; + container['ZIP'] = item.zipcode; + container['ChannelId'] = item.channelId; + return container; + }); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.html new file mode 100644 index 0000000..772fbd3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.html @@ -0,0 +1,48 @@ +
+
+
+ +
+
+
+
+
+ + Upload {{ label || description }} + + FORMS.GENERAL.OPTIONAL +
+
+ + + + {{ files.length == 0 ? "0 files added" : files.length + " files added" }} (Max {{ fileLimit }} files) + +
+
+ +
+
+ +
+ +
+ +
+ {{file.error}} +
+
+ +
+
+
\ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.scss new file mode 100644 index 0000000..e252f62 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.scss @@ -0,0 +1,95 @@ +input[type="file"] { + display: none; +} +.upload-disabled { + color: #ccc !important; + cursor: not-allowed !important; +} + +.custom-file-upload { + display: inline-block; + padding: 3px 6px; + color: black; + background-color: #f8f9fa; + cursor: pointer; +} + +.file-upload-header { + background-color: #005674; + color: white; + border: 1px #005674; +} + +.file-upload-footer p { + font-size: small; + font-style: italic; +} + +.file-form-item { + background-color: white; +} + +.unrounded-top { + border-top-left-radius: 0.0rem !important; + border-top-right-radius: 0.0rem !important; +} + +.rounded-last { + input { + border-bottom-left-radius: 0rem !important; + } + button { + border-bottom-right-radius: 0rem !important; + } +} + +.rounded-last:last-child { + input { + border-bottom-left-radius: 0.25rem !important; + } + button { + border-bottom-right-radius: 0.25rem !important; + } +} + +button { + cursor: pointer; +} + +.red-border { + border: 2px red solid; + height: 3.5rem; + padding-bottom: 1rem; +} + +.green-border { + border: 2px green solid; +} + +.file-error { + font-size: small; + color: red; + font-style: italic; + position: absolute; + bottom: 2px; + margin-left: .75rem; +} + +.icon-with-error { + padding-bottom: 10px; +} + +.mute-all { + opacity: 0.4; +} + +.loading-icon { + position: absolute; + left: calc(50% - 24px); + top: calc(50% - 24px); +} + +.optional-text { + margin-left: auto; + margin-right: 0.5rem; +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.spec.ts new file mode 100644 index 0000000..5747f31 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.spec.ts @@ -0,0 +1,104 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { configureTestSuite } from 'ng-bullet'; +import { FileUploadComponent } from './file-upload.component'; +import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { InputIconComponent } from '../../loan-wizard/controls/input-icon/input-icon.component'; +import { FileUploadService } from './file-upload.service'; +import { MockFileUploadService } from './file-upload.mock.service'; + +const createTestFormGroup = (formBuilder: FormBuilder): FormGroup => { + const group = formBuilder.group({file: new FormControl()}); + return group; +}; + +describe('FileUploadComponent', () => { + let component: FileUploadComponent; + let fixture: ComponentFixture; + let formBuilder: FormBuilder; + let formGroup: FormGroup; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [FormsModule, ReactiveFormsModule], + declarations: [ + FileUploadComponent, + InputIconComponent + ], + providers: [ + { + provide: FileUploadService, useValue: MockFileUploadService + } + ] + }); + }); + + beforeEach(() => { + formBuilder = TestBed.get(FormBuilder); + formGroup = createTestFormGroup(formBuilder); + fixture = TestBed.createComponent(FileUploadComponent); + component = fixture.componentInstance; + component.fileUploadService = TestBed.get(MockFileUploadService); + component.group = formGroup; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should disable file upload at 5 files', () => { + component.group.get('file').enable() + expect(component.group.get('file').disabled).toBeFalsy(); + component.setLoading(true); + component.addFile('','',''); + component.addFile('','',''); + component.addFile('','',''); + component.addFile('','',''); + component.addFile('','',''); + component.setLoading(false); + expect(component.group.get('file').disabled).toBeTruthy(); + }); + + it('should show an error when a file is too large', () => { + let event = { + target: { + files: [{name: 'test.jpg', size: component.fileSizeLimit*1024*1024 + 1}] + } + } + component.uploadFile(event); + + expect(component.files[0].error).toBeTruthy(); + expect(component.files[0].error).toBe(`File size exceeds ${component.fileSizeLimit} MB`); + }); + + it('should show an error when a file type is invalid', () => { + let event = { + target: { + files: [{name: 'test.exe', size: 10}] + } + } + + component.uploadFile(event); + + expect(component.files[0].error).toBeTruthy(); + expect(component.files[0].error).toBe("Invalid file type"); + }); + + it('should disable the next button when an upload is in progress', () => { + component.setLoading(true); + expect(component.disableNext).toBeTruthy(); + }); + + it('should disable the next button when a selected file is invalid', () => { + let event = { + target: { + files: [{name: 'test.exe', size: 10}] + } + } + + component.uploadFile(event); + + expect(component.disableNext).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.ts new file mode 100644 index 0000000..1090cb9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.component.ts @@ -0,0 +1,268 @@ +import { Component, EventEmitter, Injectable, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { Observable, Subject, Subscription } from 'rxjs'; +import { FileUploadService } from './file-upload.service'; + + +export enum AppLoaderState { + LOADER_EMPTY = 'empty', + LOADER_LOANS = 'loans', + LOADER_LOADING = 'loading' +} + +@Component({ + selector: 'lw-file-upload', + templateUrl: './file-upload.component.html', + styleUrls: ['./file-upload.component.scss'] +}) + +@Injectable({ + providedIn: 'root' +}) +export class FileUploadComponent implements OnInit, OnDestroy { + private settingsSubscription: Subscription; + private getFilesSubscription: Subscription; + private uploadFilesSubscription: Subscription; + private deleteFilesSubscription: Subscription; + private deleteAllFilesSubscription: Subscription; + private removeAllFilesSubscription: Subscription; + + public fileUploadService: FileUploadService + + public readonly uploadLimit = 5; + public readonly filesizeLimit = 10; + + @Input() + public loanId: string; + + @Input() + public section: string; + + @Input() + public description: string; + + @Input() + public label: string; + + @Input() + public sectionId = 0; + + @Input() + public group: FormGroup; + + @Input() + public optional = true; + + public file: any; + + @Output() + public sendFiles: EventEmitter; + + @Output() + public uploadingFiles: EventEmitter; + + @Input() + removeAllFilesSubject: Subject>; + + @Output() + public removedAllFiles: EventEmitter; + + public files = []; + + public loading: boolean = false; + public disableNext: boolean = false; + + public fileLimit = 5; + public allowedFileTypes: Array = ["gif", "jpg", "jpeg", "png", "pdf"]; + public fileSizeLimit = 10.0; + + constructor(fileUploadService: FileUploadService) { + this.fileUploadService = fileUploadService; + this.sendFiles = new EventEmitter(); + this.uploadingFiles = new EventEmitter(); + this.removedAllFiles = new EventEmitter(); + } + + ngOnInit() { + this.group = new FormGroup({ + file: new FormControl() + }); + this.getFiles(); + this.getSettings(); + if (this.removeAllFilesSubject) { + this.removeAllFilesSubscription = this.removeAllFilesSubject.subscribe(async resolve => { + this.removeAllFiles(); + }) + } + } + + + public ngOnDestroy() { + if (this.settingsSubscription) { + this.settingsSubscription.unsubscribe(); + } + if (this.getFilesSubscription) { + this.getFilesSubscription.unsubscribe(); + } + if (this.uploadFilesSubscription) { + this.uploadFilesSubscription.unsubscribe(); + } + if (this.deleteFilesSubscription) { + this.deleteFilesSubscription.unsubscribe(); + } + if (this.removeAllFilesSubscription) { + this.removeAllFilesSubscription.unsubscribe(); + } + if (this.deleteAllFilesSubscription) { + this.deleteAllFilesSubscription.unsubscribe(); + } + } + + public getSettings() { + this.settingsSubscription = this.fileUploadService.getSettings().subscribe( + response => { + this.fileLimit = response.fileLimit; + this.allowedFileTypes = response.allowedFileTypes; + this.fileSizeLimit = response.fileSizeLimit; + } + ); + } + + public getFiles() { + this.setLoading(true); + + this.getFilesSubscription = this.fileUploadService.getFiles(this.loanId).subscribe( + response => { + this.files = []; + for (let file of response.files.filter(file => file.description == this.description && (file.section == this.section || file.section == null) && file.sectionId == this.sectionId)) { + this.addFile(file.fileId, file.name, file.description); + } + this.setLoading(false); + } + ); + } + + public checkFileLimit() { + if (this.files.length >= 5) { + this.group.get('file').disable(); + } else { + this.group.get('file').enable(); + } + } + + public uploadFile(e) { + this.setLoading(true); + + if (this.files.length >= this.fileLimit || e.target.files.length == 0) { + this.setLoading(false); + return; + } + + let file = e.target.files[0]; + + // Clear file input + e.target.value = ''; + + let fileErrors = [] + + if (!this.fileSizeIsValid(file)) { + fileErrors.push(`File size exceeds ${this.fileSizeLimit} MB`); + } + + if (!this.fileExtensionIsValid(file)) { + fileErrors.push(`Invalid file type`); + } + + if (fileErrors.length > 0) { + this.addFile(null, file.name, this.description, fileErrors.join("; ")); + this.setLoading(false); + return; + } + + + this.uploadFilesSubscription = this.fileUploadService.uploadFile(this.loanId, this.section, this.sectionId, this.description, file).subscribe( + response => { + this.addFile(response.fileId, file.name, this.description); + this.setLoading(false); + }, (error) => { + this.addFile(null, file.name, this.description, error.message); + this.setLoading(false); + } + ); + } + + public deleteFile(file) { + this.setLoading(true); + + if (file.invalid) { + this.removeFile(file); + this.setLoading(false); + return; + } + + this.deleteFilesSubscription = this.fileUploadService.deleteFile(this.loanId, file.fileId).subscribe( + response => { + this.removeFile(file); + this.setLoading(false); + return true; + } + ); + } + + public addFile(fileId, name, description, error = null) { + this.files.push({ fileId, name, description, valid: error == null, invalid: error != null, dirty: true, error }); + this.sendFiles.emit(this.files); + } + + public removeFile(file) { + this.files = this.files.filter(f => f.name != file.name || f.fileId != file.fileId); + this.sendFiles.emit(this.files); + } + + public setLoading(loading: boolean) { + this.loading = loading; + this.uploadingFiles.emit(loading); + this.disableNext = true; + if (loading) { + this.group.get('file').disable() + } else { + if (this.files.length < this.fileLimit) { + this.group.get('file').enable() + } else { + this.group.get('file').disable() + } + this.disableNext = this.files.filter(file => file.invalid == true).length > 0; + } + } + + public fileExtensionIsValid(file: File): boolean { + let validExtension = false; + this.allowedFileTypes.forEach(extension => { + if (file.name.toLowerCase().endsWith(extension.toLowerCase())) { + validExtension = true; + } + }) + return validExtension; + } + + public fileSizeIsValid(file: File): boolean { + return file.size <= this.fileSizeLimit * 1024 * 1024; + } + + public removeAllFiles() { + if(this.deleteAllFilesSubscription) { + return; + } + + this.deleteAllFilesSubscription = this.fileUploadService.deleteFiles(this.loanId, this.files).subscribe((result) => { + this.files = [] + this.setLoading(false); + this.sendFiles.emit(this.files); + this.removedAllFiles.emit(true); + this.deleteAllFilesSubscription.unsubscribe(); + }); + + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.mock.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.mock.service.ts new file mode 100644 index 0000000..7100a37 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.mock.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { IFileUploadService } from './file-upload.service'; + +@Injectable({ + providedIn: 'root' +}) +export class MockFileUploadService implements IFileUploadService { + + // This is a mock so we don't use HttpClient. + constructor() { } + + getSettings(): Observable { + return of(null); + } + + getFiles(loanId: string): Observable { + return of(null); + } + + uploadFile(loanId: string, section: string, idSuffix: number, description: string, file: File): Observable { + return of(null); + } + + deleteFile(loanId: string, fileId: string): Observable { + return of(null); + } + + deleteFiles(loanId: string, files: any[]): Observable { + return of(null); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.service.ts new file mode 100644 index 0000000..f4cb968 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/file-upload/file-upload.service.ts @@ -0,0 +1,133 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { environment } from '../../../environments/environment'; +import { catchError } from 'rxjs/operators'; + +export interface IFileUploadService { + getSettings(): Observable; + + getFiles(loanId: string): Observable; + + uploadFile(loanId: string, section: string, idSuffix: number, description: string, file: File): Observable; + + deleteFile(loanId: string, fileId: string): Observable; + + deleteFiles(loanId: string, files: any[]): Observable; +} + +@Injectable({ + providedIn: 'root' +}) +export class FileUploadService implements IFileUploadService { + + constructor(private httpClient: HttpClient) { } + + public buildGetSettingsUrl(): string { + return environment.urlBase + `/Storage/Settings` + } + + public buildGetUrl(loanId: string): string { + return environment.urlBase + `/Storage/Documents/${loanId}` + } + + public buildUploadUrl(loanId: string): string { + return environment.urlBase + `/Storage/Documents/${loanId}` + } + + public buildDeleteUrl(loanId: string, fileId: string): string { + return environment.urlBase + `/Storage/Documents/${loanId}/${fileId}` + } + + public buildDeleteFilesUrl(loanId: string): string { + return environment.urlBase + `/Storage/Documents/DeleteAll/${loanId}` + } + + public getSettings(): Observable { + let observable: Observable; + + observable = this.httpClient.get(this.buildGetSettingsUrl(), {withCredentials: true}) + .pipe(catchError(err => { + if (err.status === 500) { + throw new Error('Error when getting settings'); + } else { + if(err.error.error) { + throw new Error(err.error.error); + } else { + throw new Error('Error when getting settings') + } + } + })); + + return observable; + } + + public getFiles(loanId: string): Observable { + let observable: Observable; + + observable = this.httpClient.get(this.buildGetUrl(loanId), {withCredentials: true}) + .pipe(catchError(err => { + if (err.status === 500) { + throw new Error('Error when getting files') + } else { + if(err.error.error) { + throw new Error(err.error.error); + } else { + throw new Error('Error when getting files') + } + } + } + )); + + return observable; + } + + public uploadFile(loanId: string, section: string, idSuffix: number, description: string, file: File): Observable { + let observable: Observable; + + var formdata = new FormData(); + formdata.append("File", file); + formdata.append("Section", section); + formdata.append("SectionId", idSuffix.toString()); + formdata.append("Description", description); + + observable = this.httpClient.post(this.buildUploadUrl(loanId), formdata, {withCredentials: true}) + .pipe(catchError(err => { + if (err.status === 500) { + throw new Error('Error when uploading file'); + } else { + if(err.error.error) { + throw new Error(err.error.error); + } else { + throw new Error('Error when uploading file') + } + } + })); + + return observable; + } + + public deleteFile(loanId: string, fileId: string): Observable { + let observable: Observable; + + observable = this.httpClient.delete(this.buildDeleteUrl(loanId, fileId), {withCredentials: true}) + .pipe(catchError(err => { + throw new Error('Error when deleting file') + })); + + return observable; + } + + public deleteFiles(loanId: string, files: any[]) { + let observable: Observable; + + let fileIds = []; + files.forEach((file) => fileIds.push(file.fileId)) + + var formdata = new FormData(); + formdata.append("files", JSON.stringify(fileIds)); + + observable = this.httpClient.post(this.buildDeleteFilesUrl(loanId), formdata, { withCredentials: true, responseType: "json" }) + return observable; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/admin-role.guard.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/admin-role.guard.spec.ts new file mode 100644 index 0000000..d32ec18 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/admin-role.guard.spec.ts @@ -0,0 +1,65 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; +import { AdminRoleGuard } from './admin-role.guard'; +import { MsAdalAngular6Module } from 'microsoft-adal-angular6'; +import { SecurityService } from '../security.service'; + +describe('Admin Role Guard', () => { + + let guard: AdminRoleGuard; + let service: SecurityService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + MsAdalAngular6Module.forRoot({ + tenant: '1234567', + clientId: '7654321', + redirectUri: 'http://localhost', + endpoints: {}, + navigateToLoginRequestUrl: false, + cacheLocation: 'localStorage' + }) + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [] + }); + }); + + beforeEach(() => { + guard = TestBed.get(AdminRoleGuard); + service = TestBed.get(SecurityService); + }); + + it('should create', () => { + expect(guard).toBeTruthy(); + }); + + it('should call the role check if user is authenticated', () => { + spyOn(service, 'isAuthenticated').and.returnValue(true); + spyOn(service, 'checkAdminRole').and.returnValue(true); + spyOn(service, 'login').and.returnValue(true); + guard.canActivate(null, null ); + expect( service.checkAdminRole).toHaveBeenCalled(); + expect( service.login).not.toHaveBeenCalled(); + }); + + it('should call the login if user is not authenticated', () => { + spyOn(service, 'isAuthenticated').and.returnValue(false); + spyOn(service, 'checkAdminRole').and.returnValue(true); + spyOn(service, 'login').and.returnValue(true); + guard.canActivate(null, null ); + expect( service.checkAdminRole).not.toHaveBeenCalled(); + expect( service.login).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/admin-role.guard.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/admin-role.guard.ts new file mode 100644 index 0000000..d2c8a8c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/admin-role.guard.ts @@ -0,0 +1,27 @@ +import { ErrorHandler, Injectable } from '@angular/core'; +import { CanActivate, CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { MsAdalAngular6Service } from 'microsoft-adal-angular6'; +import { SecurityService } from '../security.service'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class AdminRoleGuard implements CanActivate, CanActivateChild { + + constructor(private securityService: SecurityService) { } + + public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable { + if (this.securityService.isAuthenticated()) { + return this.securityService.checkAdminRole(); + } else { + this.securityService.login(); + } + return false; + } + + public canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable { + return this.canActivate(childRoute, state); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/app-timeout.guard.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/app-timeout.guard.spec.ts new file mode 100644 index 0000000..00e2067 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/app-timeout.guard.spec.ts @@ -0,0 +1,36 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { AppTimeoutGuard } from './app-timeout.guard'; +import { configureTestSuite } from 'ng-bullet'; + +describe('App Timeout Guard', () => { + + let service: AppTimeoutGuard; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [] + }); + }); + + beforeEach(() => { + service = TestBed.get(AppTimeoutGuard); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/app-timeout.guard.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/app-timeout.guard.ts new file mode 100644 index 0000000..a1e371f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/app-timeout.guard.ts @@ -0,0 +1,23 @@ +import { ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { LoanWizardComponent } from '../../loan-wizard/loan-wizard.component'; + +@Injectable({ + providedIn: 'root' +}) +export class AppTimeoutGuard implements CanDeactivate { + + private acceptableRoutes = ['/select-loan', '/admin']; + constructor() { + } + + public canDeactivate( + component: LoanWizardComponent, + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, + nextState: RouterStateSnapshot + ): Observable | Promise | boolean { + return (this.acceptableRoutes.indexOf(nextState.url) > -1); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/guard-manager.guard.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/guard-manager.guard.spec.ts new file mode 100644 index 0000000..a23efc0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/guard-manager.guard.spec.ts @@ -0,0 +1,48 @@ +import { GuardManager } from './guard-manager.guard'; +import { AdminRoleGuard } from './admin-role.guard'; +import { AuthenticationGuard } from 'microsoft-adal-angular6'; +import { environment } from 'src/environments/environment'; +import { MemberLoginGuard } from './member-login.guard'; +import { TestRoleGuard } from './test-role.guard'; + + +describe('GuardManager', () => { + let originalTestGuard: boolean; + let originalMemberGuard: boolean; + + beforeEach(() => { + originalTestGuard = environment.testGuard; + originalMemberGuard = environment.memberGuard; + }); + + afterEach(() => { + environment.testGuard = originalTestGuard; + environment.memberGuard = originalMemberGuard; + }); + + it('should determine correct guards for admin', () => { + expect(GuardManager.determineAdminGuards()).toEqual([AuthenticationGuard, AdminRoleGuard]); + }); + + it('should determine correct guards for loanwizard', () => { + environment.memberGuard = true; + environment.testGuard = false; + expect(GuardManager.determineLoanWizardGuards()).toEqual([MemberLoginGuard]); + environment.testGuard = true; + expect(GuardManager.determineLoanWizardGuards()).toEqual([AuthenticationGuard, TestRoleGuard, MemberLoginGuard]); + }); + + it('should determine correct guards for member', () => { + environment.testGuard = false; + expect(GuardManager.determineMemberGuards()).toEqual([]); + environment.testGuard = true; + expect(GuardManager.determineMemberGuards()).toEqual([AuthenticationGuard]); + }); + + it('should determine correct guards select loan', () => { + environment.testGuard = false; + expect(GuardManager.determineSelectLoanGuards()).toEqual([]); + environment.testGuard = true; + expect(GuardManager.determineSelectLoanGuards()).toEqual([AuthenticationGuard, TestRoleGuard]); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/guard-manager.guard.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/guard-manager.guard.ts new file mode 100644 index 0000000..1e0d48d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/guard-manager.guard.ts @@ -0,0 +1,44 @@ +import { environment } from 'src/environments/environment'; +import { AuthenticationGuard } from 'microsoft-adal-angular6'; +import { TestRoleGuard } from './test-role.guard'; +import { MemberLoginGuard } from './member-login.guard'; +import { AdminRoleGuard } from './admin-role.guard'; + +export class GuardManager { + + public static determineLoanWizardGuards(): any[] { + const guards = []; + if (environment.testGuard) { + guards.push(AuthenticationGuard); + guards.push(TestRoleGuard); + } + if (environment.memberGuard) { + guards.push(MemberLoginGuard); + } + return guards; + } + + public static determineAdminGuards(): any[] { + const guards = []; + guards.push(AuthenticationGuard); + guards.push(AdminRoleGuard); + return guards; + } + + public static determineMemberGuards(): any[] { + const guards = []; + if (environment.testGuard) { + guards.push(AuthenticationGuard); + } + return guards; + } + + public static determineSelectLoanGuards(): any[] { + const guards = []; + if (environment.testGuard) { + guards.push(AuthenticationGuard); + guards.push(TestRoleGuard); + } + return guards; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/loan-route.guard.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/loan-route.guard.spec.ts new file mode 100644 index 0000000..d12bbc3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/loan-route.guard.spec.ts @@ -0,0 +1,43 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { LoanRouteGuard } from './loan-route.guard'; +import { configureTestSuite } from 'ng-bullet'; +import { CreditApplicationDaoService } from '../credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../testing/shared-mocks/credit-application-dao.mock.service'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('Loan Route Guard', () => { + + let service: LoanRouteGuard; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + TranslateModule.forRoot(), + ReactiveFormsModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [ + ], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(LoanRouteGuard); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/loan-route.guard.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/loan-route.guard.ts new file mode 100644 index 0000000..27dfd9a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/loan-route.guard.ts @@ -0,0 +1,23 @@ +import { ActivatedRouteSnapshot, CanActivateChild, RouterStateSnapshot } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { ApplicationWizardService } from '../../loan-wizard/application-wizard.service'; + +@Injectable({ + providedIn: 'root' +}) +export class LoanRouteGuard implements CanActivateChild { + + constructor(private applicationWizardService: ApplicationWizardService) { + } + + public canActivateChild( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable | Promise | boolean { + const requestedPage = route.routeConfig.path; + const canVisit = this.applicationWizardService.canVisitPage(requestedPage); + if (canVisit) { this.applicationWizardService.displayWaypointsForPage(requestedPage); } + return canVisit; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-login.guard.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-login.guard.spec.ts new file mode 100644 index 0000000..9779b01 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-login.guard.spec.ts @@ -0,0 +1,61 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; +import { TranslateModule } from '@ngx-translate/core'; +import { MemberLoginGuard } from './member-login.guard'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { RouterStateSnapshot, Router } from '@angular/router'; +import { CookieUtil } from '../cookie/cookie-util'; +import { environment } from '../../../environments/environment'; + +describe('Member Login Guard', () => { + + let guard: MemberLoginGuard; + + class RouterStub { + url = ''; + navigate(commands: any[], extras?: any) { } + } + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + TranslateModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [{ provide: Router, useClass: RouterStub }] + }); + }); + + beforeEach(() => { + guard = TestBed.get(MemberLoginGuard); + }); + + it('should create', () => { + expect(guard).toBeTruthy(); + }); + + it('should resolve route when auth cookie is true', () => { + spyOn(CookieUtil, 'hasAuthCookie').and.returnValue(true); + expect(guard.canActivate(null, {url: 'testUrl', root: {queryParams: {}}})).toBe(true); + }); + + it('should reroute when auth cookie is false and nocookie is false', () => { + spyOn(CookieUtil, 'hasAuthCookie').and.returnValue(false); + const originalGuardFlag = environment.memberGuard; + environment.memberGuard = true; + const result = guard.canActivate(null, {url: 'testUrl', root: {queryParams: {}}}); + environment.memberGuard = originalGuardFlag; + expect(result).toBe(false); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-login.guard.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-login.guard.ts new file mode 100644 index 0000000..48691a9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-login.guard.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild } from '@angular/router'; +import { Observable } from 'rxjs'; +import { CookieUtil } from '../cookie/cookie-util'; +import { environment } from '../../../environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class MemberLoginGuard implements CanActivate, CanActivateChild { + constructor(private router: Router) {} + + public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable { + if (environment.memberGuard === false || CookieUtil.hasAuthCookie() || state.root.queryParams['noCookie']) { + return true; + } else { + this.router.navigate(['/member'], { + queryParams: { + redirect: state.url + } + }); + return false; + } + } + + public canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable { + return this.canActivate(childRoute, state); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-verification-guard.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-verification-guard.ts new file mode 100644 index 0000000..7218a36 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-verification-guard.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, CanActivateChild } from '@angular/router'; +import { Observable, of } from 'rxjs'; +import { MemberService } from 'src/app/member/member.service'; +import { environment } from '../../../environments/environment'; +import { CookieUtil } from '../cookie/cookie-util'; + +@Injectable({ + providedIn: 'root' +}) +export class MemberVerificationGuard implements CanActivate, CanActivateChild { + private VERIFICATION_ROUTE = '/member/verification'; + + constructor(private memberService: MemberService, private router: Router) {} + + public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable { + if (environment.memberGuard) { + return this.routeToVerification(state); + } + return true; + } + + public canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable { + return this.canActivate(childRoute, state); + } + + public routeToVerification(state: RouterStateSnapshot): boolean { + if (this.memberService.shouldShowVerificationPage()) { + this.router.navigate([this.VERIFICATION_ROUTE], { + queryParams: { + redirect: state.url + } + }); + return false; + } + return true; + } +} + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-verification.guard.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-verification.guard.spec.ts new file mode 100644 index 0000000..9c9fdb3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/member-verification.guard.spec.ts @@ -0,0 +1,59 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; +import { TranslateModule } from '@ngx-translate/core'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { RouterStateSnapshot, Router } from '@angular/router'; +import { MemberService } from 'src/app/member/member.service'; +import { MemberVerificationGuard } from './member-verification-guard'; +import { MemberMeta } from 'src/app/member/models/member-meta'; +import { of } from 'rxjs'; + +describe('Member Verification Guard', () => { + + let guard: MemberVerificationGuard; + let service: MemberService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + TranslateModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + }); + }); + + beforeEach(() => { + guard = TestBed.get(MemberVerificationGuard); + service = TestBed.get(MemberService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should resolve route when we already have meta data', () => { + spyOn(service, 'shouldShowVerificationPage').and.returnValue(false); + expect(guard.canActivate(null, {url: 'testUrl', root: {queryParams: {}}})).toBe(true); + }); + + it('should route to verification', () => { + spyOn(service, 'shouldShowVerificationPage').and.returnValue(true); + spyOn(router, 'navigate'); + guard.routeToVerification({url: 'testUrl', root: {queryParams: {}}}); + expect(service.shouldShowVerificationPage).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalledWith([ '/member/verification' ], Object({ queryParams: Object({ redirect: 'testUrl' }) })); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/stepper-controller-guard.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/stepper-controller-guard.spec.ts new file mode 100644 index 0000000..777f894 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/stepper-controller-guard.spec.ts @@ -0,0 +1,67 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; +import { TranslateModule } from '@ngx-translate/core'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { StepperControllerGuard } from './stepper-controller-guard'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; + +describe('Stepper Controller Guard', () => { + + let guard: StepperControllerGuard; + let service: ApplicationWizardService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + TranslateModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + }); + }); + + beforeEach(() => { + guard = TestBed.get(StepperControllerGuard); + service = TestBed.get(ApplicationWizardService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(guard).toBeTruthy(); + }); + + it('should activate route with loan type', () => { + spyOn(guard, 'hasLoanType').and.returnValue(true); + spyOn(guard, 'setLoanType'); + const result = guard.canActivate(null, null); + expect(result).toBe(true); + expect(guard.hasLoanType).toHaveBeenCalled(); + expect(guard.setLoanType).toHaveBeenCalled(); + }); + + it('should route home without loan type', () => { + spyOn(guard, 'hasLoanType').and.returnValue(false); + spyOn(guard, 'routeHome'); + expect(guard.canActivate(null, null)).toBe(false); + expect(guard.hasLoanType).toHaveBeenCalled(); + expect(guard.routeHome).toHaveBeenCalled(); + }); + + it('should route home', () => { + spyOn(router, 'navigate'); + guard.routeHome(); + expect(router.navigate).toHaveBeenCalledWith([ '/' ]); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/stepper-controller-guard.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/stepper-controller-guard.ts new file mode 100644 index 0000000..d5953a2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/stepper-controller-guard.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { ApplicationWizardService } from 'src/app/loan-wizard/application-wizard.service'; + +@Injectable({ + providedIn: 'root' +}) +export class StepperControllerGuard implements CanActivate { + constructor(private router: Router, private applicationWizardService: ApplicationWizardService) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable { + if (this.hasLoanType(route)) { + this.setLoanType(route); + return true; + } + this.routeHome(); + return false; + } + + public hasLoanType(route: ActivatedRouteSnapshot): boolean { + if (route.paramMap.get('type')) { + return true; + } else { + return false; + } + } + + public setLoanType(route: ActivatedRouteSnapshot): void { + this.applicationWizardService.setLoanType(route.paramMap.get('type')); + } + + public routeHome(): void { + this.router.navigate(['/']); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/test-role.guard.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/test-role.guard.ts new file mode 100644 index 0000000..a339bb5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/test-role.guard.ts @@ -0,0 +1,27 @@ +import { ErrorHandler, Injectable } from '@angular/core'; +import { CanActivate, CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { MsAdalAngular6Service } from 'microsoft-adal-angular6'; +import { SecurityService } from '../security.service'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class TestRoleGuard implements CanActivate, CanActivateChild { + + constructor(private securityService: SecurityService) { } + + public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable { + if (this.securityService.isAuthenticated()) { + return this.securityService.checkTesterRole(); + } else { + this.securityService.login(); + } + return false; + } + + public canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable { + return this.canActivate(childRoute, state); + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/tester-role.guard.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/tester-role.guard.spec.ts new file mode 100644 index 0000000..04b59cc --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/guards/tester-role.guard.spec.ts @@ -0,0 +1,65 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; +import { MsAdalAngular6Module } from 'microsoft-adal-angular6'; +import { SecurityService } from '../security.service'; +import { TestRoleGuard } from './test-role.guard'; + +describe('Test Role Guard', () => { + + let guard: TestRoleGuard; + let service: SecurityService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + MsAdalAngular6Module.forRoot({ + tenant: '1234567', + clientId: '7654321', + redirectUri: 'http://localhost', + endpoints: {}, + navigateToLoginRequestUrl: false, + cacheLocation: 'localStorage' + }) + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [], + providers: [] + }); + }); + + beforeEach(() => { + guard = TestBed.get(TestRoleGuard); + service = TestBed.get(SecurityService); + }); + + it('should create', () => { + expect(guard).toBeTruthy(); + }); + + it('should call the role check if user is authenticated', () => { + spyOn(service, 'isAuthenticated').and.returnValue(true); + spyOn(service, 'checkTesterRole').and.returnValue(true); + spyOn(service, 'login').and.returnValue(true); + guard.canActivate(null, null ); + expect( service.checkTesterRole).toHaveBeenCalled(); + expect( service.login).not.toHaveBeenCalled(); + }); + + it('should call the login if user is not authenticated', () => { + spyOn(service, 'isAuthenticated').and.returnValue(false); + spyOn(service, 'checkTesterRole').and.returnValue(true); + spyOn(service, 'login').and.returnValue(true); + guard.canActivate(null, null ); + expect( service.checkTesterRole).not.toHaveBeenCalled(); + expect( service.login).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/http-error-reporter.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/http-error-reporter.service.ts new file mode 100644 index 0000000..81cbeac --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/http-error-reporter.service.ts @@ -0,0 +1,33 @@ +import { ErrorHandler, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from '../../environments/environment'; + +@Injectable({ + providedIn: 'root' +}) +export class HttpErrorReporter implements ErrorHandler { + + constructor(private httpClient: HttpClient) {} + + public buildErrorURL(): string { + return environment.urlBase + '/Logging/Error'; + } + + public handleError(error: Error) { + if (error['promise'] && error['rejection']) { + // Promise rejection wrapped by zone.js + error = error['rejection']; + } + + if(environment.name === 'local') { + console.log(error) + } else { + this.httpClient.post(this.buildErrorURL(), { + name: 'LoanApp Client Error', + message: error.message, + stacktrace: error.stack + }).subscribe(() => {}); + } + + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits-information.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits-information.ts new file mode 100644 index 0000000..5591c08 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits-information.ts @@ -0,0 +1,4 @@ +export enum IncomeSchedule { + HOURLY = 'Hourly', + ANNUALLY = 'Annually' +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits.service.spec.ts new file mode 100644 index 0000000..fc560ef --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits.service.spec.ts @@ -0,0 +1,92 @@ +import { TestBed } from '@angular/core/testing'; +import { IncomeLimitsService } from './income-limits.service'; +import { ApplicationWizardService } from '../../loan-wizard/application-wizard.service'; +import { LoanTypeSet } from '../loan-type'; +import { MockCreditApplicationDaoService } from '../../testing/shared-mocks/credit-application-dao.mock.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../credit-application-dao.service'; +import { UiSettings } from '../configuration/ui-settings'; +import { DefaultSettings } from '../configuration/default-settings.mock'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + + +describe('Income Limits Service', () => { + + let applicationWizardService: ApplicationWizardService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + }); + + it('should create', () => { + expect(IncomeLimitsService).toBeTruthy(); + }); + + it('should create a minimum value validator', () => { + spyOn(IncomeLimitsService, 'minValueValidator'); + + const validator = IncomeLimitsService.minValueValidator(defaultSettings.incomeLimits); + expect(IncomeLimitsService.minValueValidator).toHaveBeenCalled(); + }); + + it('should create a maximum value validator', () => { + spyOn(IncomeLimitsService, 'maxValueValidator'); + + const validator = IncomeLimitsService.maxValueValidator(defaultSettings.incomeLimits); + expect(IncomeLimitsService.maxValueValidator).toHaveBeenCalled(); + }); + + it('should not allow a value below the minimum, and allow above the minimum', () => { + + const loanMin = 0; + const oneBelowMin = loanMin - 1; + const oneAboveMin = loanMin + 1; + const validator = IncomeLimitsService.minValueValidator(defaultSettings.incomeLimits); + expect(validator(new FormControl(oneBelowMin))).toEqual({incomeValueMinimum: {min: loanMin, actual: oneBelowMin}}); + expect(validator(new FormControl(loanMin))).toEqual(null); + expect(validator(new FormControl(oneAboveMin))).toEqual(null); + }); + + it('should not allow a value above the maximum, and allow below the maximum', () => { + const loanMax = 99999999999; + const oneBelowMax = loanMax - 1; + const oneAboveMax = loanMax + 1; + const validator = IncomeLimitsService.maxValueValidator(defaultSettings.incomeLimits); + + expect(validator(new FormControl(oneAboveMax))).toEqual({incomeValueMaximum: {max: loanMax, actual: oneAboveMax}}); + expect(validator(new FormControl(loanMax))).toEqual(null); + expect(validator(new FormControl(oneBelowMax))).toEqual(null); + }); + + it('should not allow negative values', () => { + const loanMin = 0; + const minValidator = IncomeLimitsService.minValueValidator(defaultSettings.incomeLimits); + + expect(minValidator(new FormControl(loanMin))).toEqual(null); + expect(minValidator(new FormControl(-1))).toEqual({incomeValueMinimum: {min: loanMin, actual: -1}}); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits.service.ts new file mode 100644 index 0000000..c78bed6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/income-limit/income-limits.service.ts @@ -0,0 +1,64 @@ +import { AbstractControl, ValidatorFn, FormControl, FormGroup } from '@angular/forms'; +import { MaskingService } from '../masking/masking.service'; +import { IncomeSchedule } from './income-limits-information'; +import { IncomeLimits } from '../configuration/ui-settings'; + + +export class IncomeLimitsService { + + private static getSchedule(control: AbstractControl): IncomeSchedule { + const parent = control.parent; + if (parent) { + if (parent.value) { + return parent.get('schedule').value; + } + } + + return null; + } + + private static getDefaultLimit() { + return {min: 0, max: 99999999999}; + } + + public static minValueValidator(incomeLimts: IncomeLimits): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + + const value = MaskingService.unMaskDecimalNumber(control.value); + const schedule = IncomeLimitsService.getSchedule(control); + let minValue; + if (schedule) { + minValue = incomeLimts[schedule].min; + } else { + minValue = IncomeLimitsService.getDefaultLimit().min; + } + if (value >= minValue) { + return null; + } else { + return {'incomeValueMinimum': {min: minValue, actual: value}}; + } + }; + } + + public static maxValueValidator(incomeLimts: IncomeLimits): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + + const value = MaskingService.unMaskDecimalNumber(control.value); + const schedule = IncomeLimitsService.getSchedule(control); + + let maxValue; + if (schedule) { + maxValue = incomeLimts[schedule].max; + } else { + maxValue = IncomeLimitsService.getDefaultLimit().max; + } + if (value <= maxValue) { + return null; + } else { + return {'incomeValueMaximum': {max: maxValue, actual: value}}; + } + }; + } + +} + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.html new file mode 100644 index 0000000..b31f658 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.html @@ -0,0 +1,45 @@ +
+ + + +

+ + +
+ +

+
+
+
+
+ + + +

DISCLOSURE.CREDIT_CARD.TITLES.ACCEPTANCE

+ + +
+ +

DISCLOSURE.CREDIT_CARD.ACCEPTANCE

+
+
+
+
+ + + +

DISCLOSURE.HELOC.HEADER.REVIEW

+ + +
+ +
+ +
+
+ +
+
+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.spec.ts new file mode 100644 index 0000000..451a9c0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.spec.ts @@ -0,0 +1,42 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { of } from 'rxjs'; +import { configureTestSuite } from 'ng-bullet'; + +import { DisclosureBoxesComponent } from './disclosure-boxes.component'; +import { CreditCardDisclosuresPageService } from 'src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service'; + +class CreditCardDisclosuresPageServiceStub { + requestWhatYouShouldKnowPdf = jasmine.createSpy('requestWhatYouShouldKnowPdf').and.returnValue(of(new Blob())); + requestImportantTermsPdf = jasmine.createSpy('requestImportantTermsPdf').and.returnValue(of(new Blob())); +} + +describe('DisclosureBoxesComponent', () => { + let component: DisclosureBoxesComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + declarations: [DisclosureBoxesComponent], + imports: [ + NgbModule.forRoot(), + RouterTestingModule, + TranslateModule.forRoot() + ], + providers: [{ provide: CreditCardDisclosuresPageService, useClass: CreditCardDisclosuresPageServiceStub }] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DisclosureBoxesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.ts new file mode 100644 index 0000000..5abc9d5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/disclosure-boxes.component.ts @@ -0,0 +1,58 @@ +import { Component, Input, OnInit} from '@angular/core'; +import { NgbPanelChangeEvent } from '@ng-bootstrap/ng-bootstrap'; +import { CreditCardDisclosuresPageService } from 'src/app/loan-wizard/wizards/credit-card/credit-card-disclosures-page.service'; + +export interface AccordionValues { + id: string; + show: boolean; +} + +@Component({ + selector: 'lw-disclosure-boxes', + templateUrl: './disclosure-boxes.component.html' +}) +export class DisclosureBoxesComponent { + + @Input() + public showMarriedCoapplicantDisclosure: boolean; + @Input() + public isHeloc: boolean; + + public accordionArray: Array = [ + {id: 'spouse-panel', show: true}, + {id: 'accept-panel', show: true}, + {id: 'review-panel', show: true} + ]; + + constructor( + private service: CreditCardDisclosuresPageService + ) { + this.showMarriedCoapplicantDisclosure = false; + this.isHeloc = false; + } + + public updateAccordion($event: NgbPanelChangeEvent) { + this.accordionArray.forEach( (accordion) => { + if (accordion.id === $event.panelId) { + accordion.show = $event.nextState; + } + }); + } + + public openFileKnow(){ + var fileURL: string; + this.service.requestWhatYouShouldKnowPdf().subscribe((response)=>{ + let file = new Blob([response], { type: 'application/pdf' }); + fileURL = URL.createObjectURL(file); + window.open(fileURL); + }); + } + + public openFileCredit(){ + var fileURL: string; + this.service.requestImportantTermsPdf().subscribe((response)=>{ + let file = new Blob([response], { type: 'application/pdf' }); + fileURL = URL.createObjectURL(file); + window.open(fileURL); + }); } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.html new file mode 100644 index 0000000..ab4ba0e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.html @@ -0,0 +1 @@ + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.scss new file mode 100644 index 0000000..a7f8069 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.scss @@ -0,0 +1,3 @@ +.legal-text { + margin: 1.0em; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.spec.ts new file mode 100644 index 0000000..6856585 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.spec.ts @@ -0,0 +1,35 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { Component } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; +import { TcpaComponent } from './tcpa.component'; + +@Component({ + selector: `lw-test-parent-component`, + template: `` +}) +class TestParentComponent { +} + +describe('Legal Message Component', () => { + let component: TestParentComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ NgbModule.forRoot(), TranslateModule.forRoot()], + declarations: [ TcpaComponent, TestParentComponent ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TestParentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.ts new file mode 100644 index 0000000..d94eff9 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/legal-message/tcpa.component.ts @@ -0,0 +1,12 @@ +import { Component, } from '@angular/core'; + +@Component({ + selector: 'lw-tcpa', + templateUrl: './tcpa.component.html', + styleUrls: ['./tcpa.component.scss'] +}) +export class TcpaComponent { + + constructor() { } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limit.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limit.ts new file mode 100644 index 0000000..9ec617c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limit.ts @@ -0,0 +1,4 @@ +export class MinMaxLimit { + min: number; + max: number; + } \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limits.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limits.service.spec.ts new file mode 100644 index 0000000..7764f3d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limits.service.spec.ts @@ -0,0 +1,105 @@ +import { TestBed } from '@angular/core/testing'; +import { LoanLimitsService } from './loan-limits.service'; +import { ApplicationWizardService } from '../../loan-wizard/application-wizard.service'; +import { LoanTypeSet } from '../loan-type'; +import { MockCreditApplicationDaoService } from '../../testing/shared-mocks/credit-application-dao.mock.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { CreditApplicationDaoService } from '../credit-application-dao.service'; +import { ConfigurationService } from '../configuration/configuration.service'; +import { UiSettings } from '../configuration/ui-settings'; +import { DefaultSettings } from '../configuration/default-settings.mock'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + + +describe('Loan Limits Service', () => { + + let applicationWizardService: ApplicationWizardService; + let configurationService: ConfigurationService; + const defaultSettings: UiSettings = DefaultSettings; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + declarations: [ + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ] + }); + }); + + beforeEach(() => { + applicationWizardService = TestBed.get(ApplicationWizardService); + applicationWizardService.setLoanType(LoanTypeSet[0].id); + configurationService = TestBed.get(ConfigurationService); + configurationService.saveConfiguration(defaultSettings); + }); + + it('should create', () => { + expect(LoanLimitsService).toBeTruthy(); + }); + + it('should create a minimum value validator', () => { + spyOn(LoanLimitsService, 'minValueValidator'); + const loanType = applicationWizardService.getGroupValues('loanType'); + const loanCategory = applicationWizardService.getFieldValue('loanType').category; + const validator = LoanLimitsService.minValueValidator(loanCategory, loanType, configurationService); + expect(LoanLimitsService.minValueValidator).toHaveBeenCalled(); + }); + + it('should create a maximum value validator', () => { + spyOn(LoanLimitsService, 'maxValueValidator'); + const loanType = applicationWizardService.getGroupValues('loanType'); + const loanCategory = applicationWizardService.getFieldValue('loanType').category; + const validator = LoanLimitsService.maxValueValidator(loanCategory, loanType, configurationService); + expect(LoanLimitsService.maxValueValidator).toHaveBeenCalled(); + }); + + it('should not allow a value below the minimum, and allow above the minimum', () => { + applicationWizardService.setLoanType(LoanTypeSet[4].id); + const loanType = applicationWizardService.getGroupValues('loanType'); + const loanMin = configurationService.getConfigurationByField('limits')[loanType.apiCode].min; + const oneBelowMin = loanMin - 1; + const oneAboveMin = loanMin + 1; + const loanCategory = applicationWizardService.getFieldValue('loanType').category; + const validator = LoanLimitsService.minValueValidator(loanCategory, loanType, configurationService); + + expect(validator(new FormControl(oneBelowMin))).toEqual({loanValueMinimum: {min: loanMin, actual: oneBelowMin}}); + expect(validator(new FormControl(loanMin))).toEqual(null); + expect(validator(new FormControl(oneAboveMin))).toEqual(null); + }); + + it('should not allow a value above the maximum, and allow below the maximum', () => { + applicationWizardService.setLoanType(LoanTypeSet[4].id); + const loanType = applicationWizardService.getGroupValues('loanType'); + const loanMax = configurationService.getConfigurationByField('limits')[loanType.apiCode].max; + const oneBelowMax = loanMax - 1; + const oneAboveMax = loanMax + 1; + const loanCategory = applicationWizardService.getFieldValue('loanType').category; + const validator = LoanLimitsService.maxValueValidator(loanCategory, loanType, configurationService); + expect(validator(new FormControl(oneAboveMax))).toEqual({loanValueMaximum: {max: loanMax, actual: oneAboveMax}}); + expect(validator(new FormControl(loanMax))).toEqual(null); + expect(validator(new FormControl(oneBelowMax))).toEqual(null); + }); + + it('should not allow negative values', () => { + applicationWizardService.setLoanType(LoanTypeSet[4].id); + const loanType = applicationWizardService.getGroupValues('loanType'); + const loanMin = configurationService.getConfigurationByField('limits')[loanType.apiCode].min; + const loanCategory = applicationWizardService.getFieldValue('loanType').category; + const minValidator = LoanLimitsService.minValueValidator(loanCategory, loanType, configurationService); + expect(minValidator(new FormControl(loanMin))).toEqual(null); + expect(minValidator(new FormControl(-1))).toEqual({loanValueMinimum: {min: loanMin, actual: -1}}); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limits.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limits.service.ts new file mode 100644 index 0000000..d32fa29 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-limit/loan-limits.service.ts @@ -0,0 +1,55 @@ +import { AbstractControl, ValidatorFn } from '@angular/forms'; +import { MaskingService } from '../masking/masking.service'; +import { Injectable } from '@angular/core'; +import { LoanType } from '../loan-type'; +import { LoanCategory } from '../loan-type/loan-category'; +import { ConfigurationService } from '../configuration/configuration.service'; + + +@Injectable({ + providedIn: 'root' +}) +export class LoanLimitsService { + + public static minValueValidator(loanCategory: LoanCategory, loanType: LoanType, configurationService: ConfigurationService): ValidatorFn { + if (loanCategory !== LoanCategory.CreditCard) { + return (control: AbstractControl): { [key: string]: any } | null => { + const value = MaskingService.unMaskNumber(control.value); + const minValue = configurationService.getConfigurationByField('limits')[loanType.apiCode].min; + if (value >= minValue) { + return null; + } else { + return {'loanValueMinimum': {min: minValue, actual: value}}; + } + }; + } else { + return (control: AbstractControl): { [key: string]: any } | null => { + const value = MaskingService.unMaskNumber(control.value); + if (value === null) { + return null; + } + }; + } + } + + public static maxValueValidator(loanCategory: LoanCategory, loanType: LoanType, configurationService: ConfigurationService): ValidatorFn { + if (loanCategory !== LoanCategory.CreditCard) { + return (control: AbstractControl): { [key: string]: any } | null => { + const value = MaskingService.unMaskNumber(control.value); + const maxValue = configurationService.getConfigurationByField('limits')[loanType.apiCode].max; + if (value <= maxValue) { + return null; + } else { + return {'loanValueMaximum': {max: maxValue, actual: value}}; + } + }; + } else { + return (control: AbstractControl): { [key: string]: any } | null => { + const value = MaskingService.unMaskNumber(control.value); + if (value === null) { + return null; + } + }; + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type.ts new file mode 100644 index 0000000..3579375 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type.ts @@ -0,0 +1,314 @@ +import { CreditCardPages } from '../loan-wizard/wizards/credit-card/credit-card-outline'; +import { EquityPages } from '../loan-wizard/wizards/equity/equity-outline'; +import { ApplicationPage } from '../loan-wizard/application-page'; +import { VehiclePages } from '../loan-wizard/wizards/vehicle/vehicle-outline'; +import { SignatureLoanPages } from '../loan-wizard/wizards/signature/signature-loan-outline'; +import { LoanTitle } from './loan-type/loan-title'; +import { LoanCode } from './loan-type/loan-code'; +import { LoanPurpose } from './loan-type/loan-purpose'; +import { LoanCategory } from './loan-type/loan-category'; + +export class LoanType { + id: string; + category: string; + title: LoanTitle; + icon: ApplicationTypeIcon; + iconClass: ApplicationIconClass; + imagePath?: string; + apiCode?: LoanCode; + message: string; + headerMessageKey: string; + shouldDecision: boolean; + pages: Array; + defaultPurpose: LoanPurpose; + requiresCollateral: boolean; + collateralType?: CollateralType; + decisionReviewKey: string; + beforeYouBeginKey?: string; + showProofOfIncomeUploadForm?: boolean; +} + +export enum CollateralType { + Boat = 'boat', + Vehicle = 'vehicle', + Home = 'home' +} + +export enum ApplicationTypeIcon { + CREDIT_CARD = 'credit-card', + HOUSE = 'home', + VEHICLE = 'car', + BOAT = 'boat', + PWC = 'pwc', + TRAIL_MH = 'tmh', + SIGN = 'signature', + SPECIAL = 'heart' +} + +export enum ApplicationIconClass { + CREDIT_CARD = 'fa fa-credit-card', + HOUSE = 'fa fa-home', + VEHICLE = 'fa fa-car', + BOAT = 'fa fa-ship', + PWC = 'fa fa-motorcycle', + TRAIL_MH = 'fa fa-truck', + SIGN = 'fa fa-pencil-alt', + SPECIAL = 'fa fa-heart-alt' +} + +export const LoanTypeSet: Array = [ + { + id: 'cb', + category: LoanCategory.CreditCard, + title: LoanTitle.CashBack, + apiCode: LoanCode.CC_CashBack, + icon: ApplicationTypeIcon.CREDIT_CARD, + iconClass: ApplicationIconClass.CREDIT_CARD, + imagePath: 'assets/cc-images/LCU_cashback.png', + message: 'COPY.TYPE_MESSAGE.CREDITCARD_CASHBACK', + headerMessageKey: 'HEADER.LOAN_MESSAGE.CREDITCARD_CASHBACK', + shouldDecision: true, + pages: CreditCardPages, + defaultPurpose: LoanPurpose.Other, + requiresCollateral: false, + decisionReviewKey: 'TEXT_CC' + }, + { + id: 're', + category: LoanCategory.CreditCard, + title: LoanTitle.Rewards, + apiCode: LoanCode.CC_Rewards, + icon: ApplicationTypeIcon.CREDIT_CARD, + iconClass: ApplicationIconClass.CREDIT_CARD, + imagePath: 'assets/cc-images/LCU_rewards.png', + message: 'COPY.TYPE_MESSAGE.CREDITCARD_REWARDS', + headerMessageKey: 'HEADER.LOAN_MESSAGE.CREDITCARD_REWARDS', + shouldDecision: true, + pages: CreditCardPages, + defaultPurpose: LoanPurpose.Other, + requiresCollateral: false, + decisionReviewKey: 'TEXT_CC' + }, + { + id: 'st', + category: LoanCategory.CreditCard, + title: LoanTitle.Student, + apiCode: LoanCode.CC_Student, + icon: ApplicationTypeIcon.CREDIT_CARD, + iconClass: ApplicationIconClass.CREDIT_CARD, + imagePath: 'assets/cc-images/LCU_student.png', + message: 'COPY.TYPE_MESSAGE.CREDITCARD_STUDENT', + headerMessageKey: 'HEADER.LOAN_MESSAGE.CREDITCARD_STUDENT', + shouldDecision: true, + pages: CreditCardPages, + defaultPurpose: LoanPurpose.Other, + requiresCollateral: false, + decisionReviewKey: 'TEXT_CC', + beforeYouBeginKey: 'BEFOREYOUBEGIN.CREDITCARD_STUDENT', + showProofOfIncomeUploadForm: true + }, + { + id: 'se', + category: LoanCategory.CreditCard, + title: LoanTitle.Secure, + apiCode: LoanCode.CC_Secure, + icon: ApplicationTypeIcon.CREDIT_CARD, + iconClass: ApplicationIconClass.CREDIT_CARD, + imagePath: 'assets/cc-images/LCU_secured.png', + message: 'COPY.TYPE_MESSAGE.CREDITCARD_SECURE', + headerMessageKey: 'HEADER.LOAN_MESSAGE.CREDITCARD_SECURE', + shouldDecision: true, + pages: CreditCardPages, + defaultPurpose: LoanPurpose.Other, + requiresCollateral: false, + decisionReviewKey: 'TEXT_CC', + showProofOfIncomeUploadForm: false + }, + { + id: 'he', + category: LoanCategory.Equity, + title: LoanTitle.HELOC, + apiCode: LoanCode.EQ_HELOC_Flex, + icon: ApplicationTypeIcon.HOUSE, + iconClass: ApplicationIconClass.HOUSE, + message: 'COPY.TYPE_MESSAGE.EQUITY_HELOC_FLEX', + headerMessageKey: 'HEADER.LOAN_MESSAGE.EQUITY_HELOC_FLEX', + shouldDecision: true, + pages: EquityPages, + defaultPurpose: LoanPurpose.Other, + requiresCollateral: true, + collateralType: CollateralType.Home, + decisionReviewKey: 'TEXT', + beforeYouBeginKey: 'BEFOREYOUBEGIN.EQUITY_HELOC_FLEX', + showProofOfIncomeUploadForm: true + }, + { + id: 'hef', + category: LoanCategory.Equity, + title: LoanTitle.HomeEquityFixed, + apiCode: LoanCode.EQ_HELOC_Fixed, + icon: ApplicationTypeIcon.HOUSE, + iconClass: ApplicationIconClass.HOUSE, + message: 'COPY.TYPE_MESSAGE.EQUITY_HELOC_FIXED', + headerMessageKey: 'HEADER.LOAN_MESSAGE.EQUITY_HELOC_FIXED', + shouldDecision: true, + pages: EquityPages, + defaultPurpose: LoanPurpose.Other, + requiresCollateral: true, + collateralType: CollateralType.Home, + decisionReviewKey: 'TEXT', + beforeYouBeginKey: 'BEFOREYOUBEGIN.EQUITY_HELOC_FIXED', + showProofOfIncomeUploadForm: true + }, + { + id: 'mtg', + category: LoanCategory.Equity, + title: LoanTitle.MortgageRefinanceExpress, + apiCode: LoanCode.MTG_REFI_Loan, + icon: ApplicationTypeIcon.HOUSE, + iconClass: ApplicationIconClass.HOUSE, + message: 'COPY.TYPE_MESSAGE.EQUITY_MORTGAGE_REF_EXPRESS', + headerMessageKey: 'HEADER.LOAN_MESSAGE.EQUITY_MORTGAGE_REF_EXPRESS', + shouldDecision: true, + pages: EquityPages, + defaultPurpose: LoanPurpose.Other, + requiresCollateral: true, + collateralType: CollateralType.Home, + decisionReviewKey: 'TEXT', + beforeYouBeginKey: 'BEFOREYOUBEGIN.EQUITY_MORTGAGE_REF_EXPRESS', + showProofOfIncomeUploadForm: true + }, + { + id: 'auto', + category: LoanCategory.Vehicle, + title: LoanTitle.Vehicle, + apiCode: LoanCode.VEHICLE_DEFAULT, + icon: ApplicationTypeIcon.VEHICLE, + iconClass: ApplicationIconClass.VEHICLE, + message: 'COPY.TYPE_MESSAGE.VEHICLE_AUTO', + headerMessageKey: 'HEADER.LOAN_MESSAGE.VEHICLE_AUTO', + shouldDecision: true, + pages: VehiclePages, + defaultPurpose: LoanPurpose.Purchase, + requiresCollateral: true, + collateralType: CollateralType.Vehicle, + decisionReviewKey: 'TEXT', + beforeYouBeginKey: 'BEFOREYOUBEGIN.VEHICLE_AUTO' + }, + // { + // id: 'mhl', + // category: LoanCategory.Equity, + // title: LoanTitle.ManufacturedHome, + // apiCode: LoanCode.MANUFACTURED_HOME_LOAN, + // icon: ApplicationTypeIcon.HOUSE, + // iconClass: ApplicationIconClass.HOUSE, + // message: 'COPY.TYPE_MESSAGE.EQUITY_MANUF_HOME', + // headerMessageKey: 'HEADER.LOAN_MESSAGE.EQUITY_MANUF_HOME', + // shouldDecision: true, + // pages: EquityPages, + // defaultPurpose: LoanPurpose.Other, + // requiresCollateral: true, + // collateralType: CollateralType.Home, + // decisionReviewKey: 'TEXT', + // beforeYouBeginKey: 'BEFOREYOUBEGIN.EQUITY_MANUFACTURED' + // }, + { + id: 'pauto', + category: LoanCategory.Vehicle, + title: LoanTitle.VehiclePreApproval, + apiCode: LoanCode.VEHICLE_PRE_APPROVAL, + icon: ApplicationTypeIcon.VEHICLE, + iconClass: ApplicationIconClass.VEHICLE, + message: 'COPY.TYPE_MESSAGE.VEHICLE_PRE_APPROVAL', + headerMessageKey: 'HEADER.LOAN_MESSAGE.VEHICLE_PRE_APPROVAL', + shouldDecision: true, + pages: VehiclePages, + defaultPurpose: LoanPurpose.Purchase, + requiresCollateral: false, + collateralType: CollateralType.Vehicle, + decisionReviewKey: 'TEXT' + }, + { + id: 'boat', + category: LoanCategory.Vehicle, + title: LoanTitle.Boat, + apiCode: LoanCode.BOAT, + icon: ApplicationTypeIcon.BOAT, + iconClass: ApplicationIconClass.BOAT, + message: 'COPY.TYPE_MESSAGE.VEHICLE_BOAT', + headerMessageKey: 'HEADER.LOAN_MESSAGE.VEHICLE_BOAT', + shouldDecision: true, + pages: VehiclePages, + defaultPurpose: LoanPurpose.Purchase, + requiresCollateral: true, + collateralType: CollateralType.Boat, + decisionReviewKey: 'TEXT', + beforeYouBeginKey: 'BEFOREYOUBEGIN.VEHICLE_BOAT' + }, + { + id: 'pwc', + category: LoanCategory.Vehicle, + title: LoanTitle.PowerSport, + apiCode: LoanCode.POWERSPORT, + icon: ApplicationTypeIcon.PWC, + iconClass: ApplicationIconClass.PWC, + message: 'COPY.TYPE_MESSAGE.VEHICLE_POWERSPORT', + headerMessageKey: 'HEADER.LOAN_MESSAGE.VEHICLE_POWERSPORT', + shouldDecision: true, + pages: VehiclePages, + defaultPurpose: LoanPurpose.Purchase, + requiresCollateral: true, + collateralType: CollateralType.Vehicle, + decisionReviewKey: 'TEXT', + beforeYouBeginKey: 'BEFOREYOUBEGIN.VEHICLE_POWERSPORT' + }, + { + id: 'tmh', + category: LoanCategory.Vehicle, + title: LoanTitle.TrailerMotorhome, + apiCode: LoanCode.TRAILER_MOTORHOME, + icon: ApplicationTypeIcon.TRAIL_MH, + iconClass: ApplicationIconClass.TRAIL_MH, + message: 'COPY.TYPE_MESSAGE.VEHICLE_MOTORHOME', + headerMessageKey: 'HEADER.LOAN_MESSAGE.VEHICLE_MOTORHOME', + shouldDecision: true, + pages: VehiclePages, + defaultPurpose: LoanPurpose.Purchase, + requiresCollateral: true, + collateralType: CollateralType.Vehicle, + decisionReviewKey: 'TEXT', + beforeYouBeginKey: 'BEFOREYOUBEGIN.VEHICLE_MOTORHOME' + }, + { + id: 'pe', + category: LoanCategory.Signature, + title: LoanTitle.Signature, + apiCode: LoanCode.SIGNATURE, + icon: ApplicationTypeIcon.SIGN, + iconClass: ApplicationIconClass.SIGN, + message: 'COPY.TYPE_MESSAGE.SIGNATURE_LOAN', + headerMessageKey: 'HEADER.LOAN_MESSAGE.SIGNATURE_LOAN', + shouldDecision: true, + pages: SignatureLoanPages, + defaultPurpose: LoanPurpose.Other, + requiresCollateral: false, + decisionReviewKey: 'TEXT' + }, + // { + // id: 'herm', + // category: LoanCategory.Equity, + // title: LoanTitle.HELOC_RateMod, + // apiCode: LoanCode.EQ_HELOC_Flex, + // icon: ApplicationTypeIcon.HOUSE, + // iconClass: ApplicationIconClass.HOUSE, + // message: 'COPY.TYPE_MESSAGE.EQUITY_HELOC_RATE_MOD', + // headerMessageKey: 'HEADER.LOAN_MESSAGE.EQUITY_HELOC_RATE_MOD', + // shouldDecision: true, + // pages: EquityPages, + // defaultPurpose: LoanPurpose.Other, + // requiresCollateral: true, + // collateralType: CollateralType.Home, + // decisionReviewKey: 'TEXT', + // beforeYouBeginKey: 'BEFOREYOUBEGIN.EQUITY_HELOC_RATE_MOD' + // } +]; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-category.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-category.ts new file mode 100644 index 0000000..bcf0f94 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-category.ts @@ -0,0 +1,6 @@ +export enum LoanCategory { + CreditCard = 'Credit Card', + Equity = 'Equity', + Vehicle = 'Vehicle', + Signature = 'Signature' + } \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-code.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-code.ts new file mode 100644 index 0000000..084aff3 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-code.ts @@ -0,0 +1,19 @@ +// String assignment is because otherwise it auto-convers to a number. +export enum LoanCode { + CC_CashBack = 'CC_CashBack', + CC_Rewards = 'CC_Rewards', + CC_Student = 'CC_Student', + CC_Secure = 'CC_Secure', + EQ_HELOC_Flex = 'EQ_HELOC_Flex', + EQ_HELOC_Fixed = 'EQ_Home_Equity_Fixed', + MTG_REFI_Loan = 'EQ_Mtg_Refi_Express', + MANUFACTURED_HOME_LOAN = 'EQ_Mfg_Home_With_or_Without_Land', + VEHICLE_DEFAULT = 'V_Vehicle', + VEHICLE_PRE_APPROVAL = 'V_VehiclePreapproval', + BOAT = 'M_Boat', + POWERSPORT = 'P_Powersport', + TRAILER_MOTORHOME = 'T_TrailerMotorhome', + SIGNATURE = 'S_Signature', + SPECIAL = 'S_Special', + EQ_HELOC_Rate_Mod = 'EQ_HELOC_Rate_Mod' + } diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-purpose.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-purpose.ts new file mode 100644 index 0000000..0b211b1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-purpose.ts @@ -0,0 +1,19 @@ +// From Swagger: Home_Improvement, Home_Purchase, Home_Refinance, Purchase_Boat, Purchase, Other, Refinance, Dental_Expense, +// Medical_Expense, Cash_Out_Refinance, Refinance_Vehicle, Consolidation, Reestablish_Credit, Purchase_Manufactured_Home +export enum LoanPurpose { + Unselected = '', + Home_Improvement = 'Home_Improvement', + Home_Purchase = 'Home_Purchase', + Home_Refinance = 'Home_Refinance', + Purchase_Boat = 'Purchase_Boat', + Purchase = 'Purchase', + Other = 'Other', + Refinance = 'Refinance', + Dental_Expense = 'Dental_Expense', + Medical_Expense = 'Medical_Expense', + Cash_Out_Refinance = 'Cash_Out_Refinance', + Refinance_Vehicle = 'Refinance_Vehicle', + Consolidation = 'Consolidation', + Reestablish_Credit = 'Reestablish_Credit', + Purchase_Manufactured_Home = 'Purchase_Manufactured_Home' +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-title.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-title.ts new file mode 100644 index 0000000..d72926b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/loan-type/loan-title.ts @@ -0,0 +1,18 @@ +export enum LoanTitle { + CashBack = 'Cash Back', + Rewards = 'Rewards', + Student = 'Student', + Secure = 'Secure', + HELOC = 'HELOC', + HomeEquityFixed = 'Home Equity Fixed', + MortgageRefinanceExpress = 'Mortgage Refinance Express', + Vehicle = 'Vehicle Auto/Motorcycle', + ManufacturedHome = 'Manufactured Home', + VehiclePreApproval = 'Vehicle Pre-Approval', + Boat = 'Boat', + PowerSport = 'Personal Water Craft', + TrailerMotorhome = 'Trailer/Motorhome', + Signature = 'Signature', + SpecialLoan = 'COVID-19', + HELOC_RateMod = 'Heloc Rate Modification' +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/masking/masking.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/masking/masking.service.spec.ts new file mode 100644 index 0000000..16c531b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/masking/masking.service.spec.ts @@ -0,0 +1,85 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { MaskingService } from './masking.service'; +import { MemberInfoFormComponent } from '../../loan-wizard/field-groups/member/member-info-form.component'; +import { TextMaskModule } from 'angular2-text-mask'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + +describe('MaskingWizardService', () => { + + let service: MaskingService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule, + TextMaskModule, + TranslateModule.forRoot() + ], + declarations: [ + MemberInfoFormComponent + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + service = TestBed.get(MaskingService); + }); + + it('should format dates correctly', () => { + const formatted = MaskingService.makeDOB('09171991'); + expect(formatted).toBe('09/17/1991'); + }); + + it('should always return a correctly formatted date of the proper length', () => { + const formatted = MaskingService.makeDOB('091719912018'); + expect(formatted).toBe('09/17/1991'); + }); + + it('should convert numbers to be more human readable', () => { + const formatted = MaskingService.makeNumber('12000500250000125'); + expect(formatted).toBe('12,000,500,250,000,125'); + }); + + it('should handle already formatted numbers', () => { + const formatted = MaskingService.makeNumber('12,250,500'); + expect(formatted).toBe('12,250,500'); + }); + + it('should format phone #\'s correctly', () => { + const formatted = MaskingService.makePhone('4148675309'); + expect(formatted).toBe('(414) 867-5309'); + }); + + it('should format SSN\'s correctly', () => { + const formatted = MaskingService.makeSSN('111223333'); + expect(formatted).toBe('111-22-3333'); + }); + + it('should undo masks', () => { + const unformattedPhone = MaskingService.unMaskNumber('414-867-5309'); + expect(unformattedPhone).toBe('4148675309'); + + const unformattedSSN = MaskingService.unMaskNumber('111-22-3333'); + expect(unformattedSSN).toBe('111223333'); + + const unformattedDate = MaskingService.unMaskNumber('04/26/1992'); + expect(unformattedDate).toBe('04261992'); + + const unformattedCurrency = MaskingService.unMaskDecimalNumber('12,250,500.65'); + expect(unformattedCurrency).toBe('12250500.65'); + }); + + it('should create a mask of proper length for no-special-characters', () => { + const masked = MaskingService.noSpecial('13 or so char'); + expect(masked.length).toBe(13); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/masking/masking.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/masking/masking.service.ts new file mode 100644 index 0000000..4d30fae --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/masking/masking.service.ts @@ -0,0 +1,68 @@ +import createNumberMask from 'text-mask-addons/dist/createNumberMask'; +import emailMask from 'text-mask-addons/dist/emailMask'; +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) + +export class MaskingService { + + public static readonly emptyPhone = '(___) ___-____'; + public static readonly phoneMask = ['(', /[1-9]/, /\d/, /\d/, ')', ' ', /\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/]; + public static readonly ssnMask = [/[0-9]/, /\d/, /\d/, '-', /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/]; + public static readonly numberMask = createNumberMask({prefix: '', suffix: ''}); + public static readonly numberMaskWithDecimal = createNumberMask({prefix: '', suffix: '', allowDecimal: true}); + public static readonly emailMask = emailMask; + public static readonly dobMask = [/[0-1]/, /[0-9]/, '/', /[0-3]/, /[0-9]/, '/', /[1-2]/, /[0-9]/, /[0-9]/, /[0-9]/]; + public static readonly zipMask = [/[0-9]/, /\d/, /\d/, /\d/, /\d/]; + public static readonly middleInitialMask = [/[a-zA-Z]/]; + public static readonly yearMask = [/[0-9]/, /\d/, /\d/, /\d/]; + + public static unMaskNumber(val) { + + if (val && val.replace) { + return val.replace(/\D+/g, ''); + } + return val; + } + + public static unMaskDecimalNumber(val) { + if (val && val.replace) { + return val.replace(/,/g, ''); + } + return val; + } + + public static makeSSN(val): string { + return val[0] + val[1] + val[2] + '-' + val[3] + val[4] + '-' + val[5] + val[6] + val[7] + val[8]; + } + + public static makePhone(val = ''): string { + if (val === undefined || val === '' || val.length < 9) { return this.emptyPhone; } + return '(' + val[0] + val[1] + val[2] + ') ' + val[3] + val[4] + val[5] + '-' + val[6] + val[7] + val[8] + val[9]; + } + + public static makeDOB(val): string { + return val[0] + val[1] + '/' + val[2] + val[3] + '/' + val[4] + val[5] + val[6] + val[7]; + } + + public static makeNumber(val): string { + return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + } + + public static noSpecial(val) { + if (!val) { + return [/[a-z0-9]+$/i]; + } + let iterable = val.split(''); + const mask = []; + iterable = iterable.filter((char) => { + return !!char.match(/[a-z0-9 ]+$/i); + }); + iterable.forEach(element => { + mask.push(/[a-z0-9 ]+$/i); + }); + return mask; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/branch-options.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/branch-options.spec.ts new file mode 100644 index 0000000..0f1bdec --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/branch-options.spec.ts @@ -0,0 +1,36 @@ +import { BranchOptions } from './branch-options'; +import { DefaultSettings } from '../configuration/default-settings.mock'; + + + +describe('Branch Options', () => { + + it('should get branch name by zip code', () => { + spyOn(BranchOptions, 'getChannelIdByZip').and.returnValue(1561591); + const result = BranchOptions.getBranchNameByZip('53033', DefaultSettings); + expect(result).toEqual('Grafton'); + }); + + it('should NOT get branch name with invalid zip code', () => { + spyOn(BranchOptions, 'getChannelIdByZip').and.returnValue(1234555); + const result = BranchOptions.getBranchNameByZip('53033', DefaultSettings); + expect(result).toEqual('Not Found'); + }); + + it('should get list of branch names', () => { + const result = BranchOptions.getBranchNames(DefaultSettings); + expect(result[0]).toEqual('ACTion Online'); + expect(result.length).toEqual(43); + }); + + it('should get channel id by zip code', () => { + const result = BranchOptions.getChannelIdByZip(DefaultSettings, '53033'); + expect(result).toEqual(1561664); + }); + + it('should NOT get channel id with invalid zip code', () => { + const result = BranchOptions.getChannelIdByZip(DefaultSettings, '66666'); + expect(result).toEqual(0); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/branch-options.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/branch-options.ts new file mode 100644 index 0000000..68c58df --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/branch-options.ts @@ -0,0 +1,35 @@ +import { UiSettings } from '../configuration/ui-settings'; + +export class BranchOptions { + + public static getBranchNameByZip(postalCode: string, settings: UiSettings): string { + const zipCode = parseInt(postalCode, 10); + const channelId = this.getChannelIdByZip(settings, zipCode + ''); + let branchName = 'Not Found'; + if (settings.branchNameMapping && settings.branchNameMapping.branchNamesByChannelId && settings.branchNameMapping.branchNamesByChannelId.length) { + settings.branchNameMapping.branchNamesByChannelId.forEach((eachMapping) => { + if (eachMapping.channelId === channelId) { + branchName = eachMapping.branchName; + } + }); + } + return branchName; + } + + public static getBranchNames(settings: UiSettings): string[] { + const branchList = settings.branchNameMapping.branchNamesByChannelId.map(value => value.branchName); + return branchList.sort(); + } + + public static getChannelIdByZip(settings: UiSettings, zipCode: string): number { + let channelId = 0; + if (settings.branchMapping && settings.branchMapping.branchCodesByZip && settings.branchMapping.branchCodesByZip.length) { + settings.branchMapping.branchCodesByZip.forEach((eachMapping) => { + if (eachMapping.zipcode === zipCode) { + channelId = eachMapping.channelId; + } + }); + } + return channelId; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/route-options.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/route-options.spec.ts new file mode 100644 index 0000000..572361f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/route-options.spec.ts @@ -0,0 +1,13 @@ +import { RouteOptions } from './route-options'; +import { LoanCode } from '../loan-type/loan-code'; + + + +describe('Route Options', () => { + + it('should get route by loan code', () => { + const result = RouteOptions.getRouteFromLoanCode(LoanCode.CC_CashBack); + expect(result).toEqual('/loan-wizard/cc/cb'); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/route-options.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/route-options.ts new file mode 100644 index 0000000..4ee25ed --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/route-options.ts @@ -0,0 +1,25 @@ +import { LoanCode } from '../loan-type/loan-code'; + +export class RouteOptions { + public static readonly loanCodeToRoute = { + [LoanCode.CC_CashBack]: '/loan-wizard/cc/cb', + [LoanCode.CC_Rewards]: '/loan-wizard/cc/re', + [LoanCode.CC_Student]: '/loan-wizard/cc/st', + [LoanCode.CC_Secure]: '/loan-wizard/cc/se', + [LoanCode.EQ_HELOC_Flex]: '/loan-wizard/eq/he', + [LoanCode.EQ_HELOC_Fixed]: '/loan-wizard/eq/hef', + [LoanCode.MTG_REFI_Loan]: '/loan-wizard/eq/mtg', + // [LoanCode.MANUFACTURED_HOME_LOAN]: '/loan-wizard/eq/mhl', + [LoanCode.VEHICLE_DEFAULT]: '/loan-wizard/ve/auto', + [LoanCode.VEHICLE_PRE_APPROVAL]: '/loan-wizard/ve/pauto', + [LoanCode.BOAT]: '/loan-wizard/ve/boat', + [LoanCode.POWERSPORT]: '/loan-wizard/ve/pwc', + [LoanCode.TRAILER_MOTORHOME]: '/loan-wizard/ve/tmh', + [LoanCode.SIGNATURE]: '/loan-wizard/sl/pe', + // [LoanCode.EQ_HELOC_Rate_Mod]: '/loan-wizard/eq/herm' + }; + + public static getRouteFromLoanCode(loanCode: LoanCode) { + return this.loanCodeToRoute[loanCode]; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/select-options.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/select-options.ts new file mode 100644 index 0000000..fd236f6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/select-options.ts @@ -0,0 +1,138 @@ +import { PhoneTypeEnum } from '../api-models/api-enums'; +import { LoanCode } from '../loan-type/loan-code'; + + +export class SelectOptions { + public static readonly occupancyTypes: Array<{value: any, desc: string}> = [ + {value: '', desc: 'Please Choose'}, + {value: 'OwnerOccupied', desc: 'Owner Occupied'}, + {value: 'SecondHome', desc: 'Second Home'}, + {value: 'Rental', desc: 'Rental'}, + {value: 'VacationHome', desc: 'Vacation Home'}, + {value: 'Vacant', desc: 'Vacant'} + ]; + + public static readonly employmentTypeOptions: Array<{value: any, desc: string}> = [ + {value: '', desc: 'Please Choose'}, + {value: 'FullTime', desc: 'Full time'}, + {value: 'PartTime', desc: 'Part time'}, + {value: 'Contract', desc: 'Contract'} + ]; + + public static readonly employmentScheduleOptions: Array<{value: any, desc: string}> = [ + {value: 'Hourly', desc: 'Hourly'}, + {value: 'Annually', desc: 'Salary'} + ]; + + public static readonly yearOptions: Array<{value: any, desc: string}> = [ + {value: '', desc: 'Please Choose'}, + {value: '0', desc: '0 years'}, + {value: '1', desc: '1 year'}, + {value: '2', desc: '2 years'}, + {value: '3', desc: '3 years'}, + {value: '4', desc: '4 years'}, + {value: '5', desc: '5 years'}, + {value: '6', desc: '6 years'}, + {value: '7', desc: '7 years'}, + {value: '8', desc: '8 years'}, + {value: '9', desc: '9+ years'} + ]; + + public static readonly monthOptions: Array<{value: any, desc: string}> = [ + {value: '', desc: 'Please Choose'}, + {value: '0', desc: '0 months'}, + {value: '1', desc: '1 month'}, + {value: '2', desc: '2 months'}, + {value: '3', desc: '3 months'}, + {value: '4', desc: '4 months'}, + {value: '5', desc: '5 months'}, + {value: '6', desc: '6 months'}, + {value: '7', desc: '7 months'}, + {value: '8', desc: '8 months'}, + {value: '9', desc: '9 months'}, + {value: '10', desc: '10 months'}, + {value: '11', desc: '11 months'} + ]; + + public static readonly residenceStatusPast: Array<{value: any, desc: string}> = [ + {value: '0', desc: 'I owned this home'}, + {value: '1', desc: 'I rented this home'}, + {value: '2', desc: 'Other'} + ]; + + public static readonly residenceStatusPresent: Array<{value: any, desc: string}> = [ + {value: '0', desc: 'I own this home'}, + {value: '1', desc: 'I rent this home'}, + {value: '2', desc: 'Other'} + ]; + + public static readonly purposeOptions: Array<{value: any, desc: string}> = [ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 'Home_Refinance', desc: 'Refinance'}, + {value: 'Home_Improvement', desc: 'Home Improvement'}, + {value: 'Home_Purchase', desc: 'Home Purchase'}, + {value: 'Other', desc: 'Other'}, + ]; + + public static readonly purposeOptionsAdditionalRefinance: Array<{value: any, desc: string}> = [ + ...SelectOptions.purposeOptions, + {value: 'Cash_Out_Refinance', desc: 'Refinance Cash-Out'} + ]; + + public static readonly phoneTypeOptions: Array<{value: any, desc: string}> = [ + {value: PhoneTypeEnum.Home, desc: 'Home'}, + {value: PhoneTypeEnum.Mobile, desc: 'Mobile'} + ]; + + public static readonly suffixOptions: Array<{value: any, desc: string}> = [ + {value: '', desc: 'Choose One'}, + {value: 'II', desc: 'II'}, + {value: 'III', desc: 'III'}, + {value: 'IV', desc: 'IV'}, + {value: 'V', desc: 'V'}, + {value: 'Jr', desc: 'Jr'}, + {value: 'Sr', desc: 'Sr'} + ]; + + public static readonly realEstateOptions: Array<{value: any, desc: string}> = [ + {value: '', desc: 'Please Choose'}, + {value: 'SingleFamily', desc: 'Single Family Home'}, + {value: 'Condo', desc: 'Condo'}, + {value: 'Townhouse', desc: 'Duplex'}, + {value: 'Family2_4', desc: 'Multi Family 2-4'}, + {value: 'Other', desc: 'Other'} + ]; + + public static readonly manufacturedHomeRealEstateOptions: Array<{value: any, desc: string}> = [ + {value: '', desc: 'Please Choose'}, + {value: 'Single', desc: 'Single'}, + {value: 'Double', desc: 'Double'}, + {value: 'Triple', desc: 'Triple'}, + {value: 'Quad', desc: 'Quad'} + ]; + + public static readonly loanOptions: Array<{value: any, desc: string}> = [ + {value: 'ALL', desc: 'ALL'}, + {value: LoanCode.CC_CashBack, desc: 'CC CashBack'}, + {value: LoanCode.CC_Rewards, desc: 'CC Rewards'}, + {value: LoanCode.CC_Student, desc: 'CC Student'}, + {value: LoanCode.CC_Secure, desc: 'CC Secure'}, + {value: LoanCode.EQ_HELOC_Flex, desc: 'HELOC Flex'}, + {value: LoanCode.EQ_HELOC_Fixed, desc: 'Home Equity Fixed'}, + {value: LoanCode.MTG_REFI_Loan, desc: 'Mtg Refi Express'}, + {value: LoanCode.MANUFACTURED_HOME_LOAN, desc: 'Manufactured Home'}, + {value: LoanCode.VEHICLE_DEFAULT, desc: 'Vehicle'}, + {value: LoanCode.VEHICLE_PRE_APPROVAL, desc: 'Vehicle Preapproval'}, + {value: LoanCode.BOAT, desc: 'Boat'}, + {value: LoanCode.POWERSPORT, desc: 'Powersport'}, + {value: LoanCode.TRAILER_MOTORHOME, desc: 'Trailer Motorhome'}, + {value: LoanCode.SIGNATURE, desc: 'Signature'}, + {value: LoanCode.SPECIAL, desc:'Special'} + ]; + + public static readonly modFeeOptions: Array<{value: any, desc: string}> = [ + {value: 'Landmark Checking Account', desc: 'Landmark Checking Account'}, + {value: 'Landmark Savings Account', desc: 'Landmark Savings Account'}, + {value: 'Other', desc: 'Other'}, + ]; +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/term-options.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/term-options.spec.ts new file mode 100644 index 0000000..c98b56c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/term-options.spec.ts @@ -0,0 +1,58 @@ +import { TermOption } from '../configuration/ui-settings'; +import { TermOptions } from './term-options'; +import { LoanCode } from '../loan-type/loan-code'; + + +let mockTerms: Array; + +describe('Term Options', () => { + + beforeEach(() => { + mockTerms = [ + { + 'value': 12, + 'desc': '12 months' + }, + { + 'value': 24, + 'desc': '24 months' + }, + { + 'value': 36, + 'desc': '36 months' + }, + { + 'value': 48, + 'desc': '48 months' + } + ]; + }); + + it('should insert text decoration on first value of list', () => { + const result = TermOptions.insertTextDecorations(mockTerms); + expect(result[0].desc).toBe('Up to 12 months'); + expect(result[1].desc).toBe('24 months'); + }); + + it('should insert text decoration for a vehicle loan', () => { + const result = TermOptions.insertTextDecorationsByLoan(mockTerms, LoanCode.VEHICLE_DEFAULT); + expect(result[0].desc).toBe('Up to 12 months'); + expect(result[1].desc).toBe('24 months'); + }); + + it('should NOT insert text decoration for a signature loan', () => { + const result = TermOptions.insertTextDecorationsByLoan(mockTerms, LoanCode.SIGNATURE); + expect(result[0].desc).toBe('12 months'); + expect(result[1].desc).toBe('24 months'); + }); + + it('should return term options with year for a vehicle loan', () => { + const result = TermOptions.determineTermOptionsUiSettingsField(LoanCode.VEHICLE_DEFAULT); + expect(result).toBe('termOptions'); + }); + + it('should return term options WITHOUT a year for a signature loan loan', () => { + const result = TermOptions.determineTermOptionsUiSettingsField(LoanCode.SIGNATURE); + expect(result).toBe('termOptionsNoYear'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/term-options.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/term-options.ts new file mode 100644 index 0000000..a284b49 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/term-options.ts @@ -0,0 +1,45 @@ +import { LoanCode } from '../loan-type/loan-code'; +import { TermOption } from '../configuration/ui-settings'; + +export class TermOptions { + + public static readonly defaultTermOption = {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}; + + public static readonly uiSettingsKeyWithYear = 'termOptions'; + public static readonly uiSettingsKeyWithoutYear = 'termOptionsNoYear'; + + public static readonly loanCodeToUiSettingKey = { + [LoanCode.VEHICLE_DEFAULT]: 'termsByYear', + [LoanCode.BOAT]: 'boatLoansByAmount', + [LoanCode.POWERSPORT]: 'powersportByAmount', + [LoanCode.TRAILER_MOTORHOME]: 'mfdHomeAndRvByAmount', + [LoanCode.MTG_REFI_Loan]: 'mortgageRefiExpress', + [LoanCode.EQ_HELOC_Flex]: 'homeEquity', + [LoanCode.EQ_HELOC_Fixed]: 'homeEquity', + [LoanCode.MANUFACTURED_HOME_LOAN]: 'mfdHomeAndRvByAmount', + [LoanCode.VEHICLE_PRE_APPROVAL]: 'vehiclePreApproval', + [LoanCode.SIGNATURE]: 'signatureByAmount', + [LoanCode.SPECIAL]: 'special', + [LoanCode.EQ_HELOC_Rate_Mod]: 'helocRateMod' + }; + + public static insertTextDecorations(list: Array): Array<{ value: string, desc: string }> { + const desc = 'Up to ' + list[0].desc; + list.splice(0, 1, {value: list[0].value, desc: desc}); + return list; + } + + public static insertTextDecorationsByLoan(list: Array, loanCode: LoanCode): Array<{ value: string, desc: string }> { + if (loanCode === LoanCode.SIGNATURE || loanCode === LoanCode.EQ_HELOC_Flex || loanCode === LoanCode.MTG_REFI_Loan) { + return list; + } + return this.insertTextDecorations(list); + } + + public static determineTermOptionsUiSettingsField(loanCode: LoanCode): string { + if (loanCode === LoanCode.SIGNATURE || loanCode === LoanCode.VEHICLE_PRE_APPROVAL) { + return this.uiSettingsKeyWithoutYear; + } + return this.uiSettingsKeyWithYear; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/vehicle-options.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/vehicle-options.spec.ts new file mode 100644 index 0000000..58ea108 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/vehicle-options.spec.ts @@ -0,0 +1,29 @@ +import { VehicleOptions } from './vehicle-options'; +import { LoanCode } from '../loan-type/loan-code'; + + + +describe('Vehicle Options', () => { + + it('should get vehicle types for vehicle', () => { + const result = VehicleOptions.getVehicleTypesByLoanCode(LoanCode.VEHICLE_DEFAULT); + expect(result).toBe(VehicleOptions.vehicleTypeOptions); + }); + + it('should get vehicle types for powersport', () => { + const result = VehicleOptions.getVehicleTypesByLoanCode(LoanCode.POWERSPORT); + expect(result).toBe(VehicleOptions.pwcTypeOptions); + }); + + it('should try to get vehicle types for boat and return null', () => { + const result = VehicleOptions.getVehicleTypesByLoanCode(LoanCode.BOAT); + expect(result).toBe(null); + }); + + it('should get all vehicle types', () => { + const result = VehicleOptions.getAllVehicleTypes(); + expect(result).toEqual([...VehicleOptions.vehicleTypeOptions, ...VehicleOptions.pwcTypeOptions, + ...VehicleOptions.trailerTypeOptions, ...VehicleOptions.boatTypeOptions]); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/vehicle-options.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/vehicle-options.ts new file mode 100644 index 0000000..427bfbe --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/options/vehicle-options.ts @@ -0,0 +1,75 @@ +import { LoanPurpose } from '../loan-type/loan-purpose'; +import { LoanCode } from '../loan-type/loan-code'; +import { VehicleType } from '../vehicle-type/vehicle-type'; +import { TrailerType } from '../vehicle-type/trailer-type'; +import { MotorType } from '../vehicle-type/motor-type'; + +export class VehicleOptions { + public static readonly vehicleConditionOptions: Array<{value: any, desc: string}> = [ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 'New', desc: 'FORMS.VEHICLE.CONDITIONS.NEW'}, + {value: 'Used', desc: 'FORMS.VEHICLE.CONDITIONS.USED'} + ]; + + public static readonly vehicleLoanPurposeOptions: Array<{value: any, desc: string}> = [ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: LoanPurpose.Refinance_Vehicle, desc: 'FORMS.VEHICLE.REFINANCE'}, + {value: LoanPurpose.Purchase, desc: 'FORMS.VEHICLE.PURCHASE'} + ]; + + public static readonly vehicleLoanPurposeOptionsVehicle: Array<{value: any, desc: string}> = [ + ...VehicleOptions.vehicleLoanPurposeOptions, + {value: LoanPurpose.Cash_Out_Refinance, desc: 'FORMS.VEHICLE.CASHOUT_REFINANCE'} + ]; + + public static readonly vehicleSellerOptions: Array<{value: any, desc: string}> = [ + {value: 'Private_Party', desc: 'FORMS.VEHICLE.PRIVATE_PARTY'}, + {value: 'Dealer', desc: 'FORMS.VEHICLE.DEALERSHIP'} + ]; + + public static readonly vehicleTypeOptions: Array<{value: VehicleType, desc: string}> = [ + {value: VehicleType.AUTO, desc: 'FORMS.VEHICLE.TYPES.AUTOMOBILE'}, + {value: VehicleType.CYCLE, desc: 'FORMS.VEHICLE.TYPES.MOTORCYCLE'}, + ]; + + public static readonly boatTypeOptions: Array<{value: VehicleType, desc: string}> = [ + {value: VehicleType.BOAT, desc: 'FORMS.VEHICLE.TYPES.BOAT'} + ]; + + public static readonly trailerTypeOptions: Array<{value: VehicleType, desc: string}> = [ + {value: VehicleType.TRAILER, desc: 'FORMS.VEHICLE.TYPES.TRAILER'} + ]; + + public static readonly pwcTypeOptions: Array<{value: VehicleType, desc: string}> = [ + {value: VehicleType.ATV, desc: 'FORMS.VEHICLE.TYPES.ATV'}, + {value: VehicleType.SNOW, desc: 'FORMS.VEHICLE.TYPES.SNOWMOBILE'}, + {value: VehicleType.PWC, desc: 'FORMS.VEHICLE.TYPES.PWC'}, + {value: VehicleType.RV, desc: 'FORMS.VEHICLE.TYPES.RV'} + ]; + + public static readonly boatTrailerTypeOptions: Array<{value: any, desc: string}> = [ + {value: TrailerType.SINGLE_AXEL, desc: 'FORMS.VEHICLE.BOAT_TRAILER_TYPE.SINGLE'}, + {value: TrailerType.TANDEM_AXEL, desc: 'FORMS.VEHICLE.BOAT_TRAILER_TYPE.TANDEM'}, + {value: TrailerType.TRI_AXLE, desc: 'FORMS.VEHICLE.BOAT_TRAILER_TYPE.TRI_AXLE'} + ]; + + public static readonly boatMotorTypeOptions: Array<{value: any, desc: string}> = [ + {value: MotorType.INBOARD, desc: 'FORMS.VEHICLE.BOAT_MOTOR_TYPE.INBOARD'}, + {value: MotorType.OUTBOARD, desc: 'FORMS.VEHICLE.BOAT_MOTOR_TYPE.OUTBOARD'}, + {value: MotorType.INBOARD_OUTBOARD, desc: 'FORMS.VEHICLE.BOAT_MOTOR_TYPE.INBOARD_OUTBOARD'} + ]; + + public static getVehicleTypesByLoanCode(loanCode: LoanCode): Array<{ value: VehicleType, desc: string }> { + if (loanCode === LoanCode.VEHICLE_DEFAULT) { + return VehicleOptions.vehicleTypeOptions; + } else if (loanCode === LoanCode.POWERSPORT) { + return VehicleOptions.pwcTypeOptions; + } + return null; + } + + public static getAllVehicleTypes(): Array<{ value: VehicleType, desc: string }> { + return [...VehicleOptions.vehicleTypeOptions, ...VehicleOptions.pwcTypeOptions, + ...VehicleOptions.trailerTypeOptions, ...VehicleOptions.boatTypeOptions]; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/page-focus/auto-focus.directive.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/page-focus/auto-focus.directive.ts new file mode 100644 index 0000000..da8ab55 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/page-focus/auto-focus.directive.ts @@ -0,0 +1,26 @@ +import { Directive, ElementRef, Input, OnInit } from '@angular/core'; + +@Directive({ + selector: '[lwAutoFocus]' +}) +export class AutoFocusDirective implements OnInit { + private focus = true; + + @Input() + public shouldRequestFocus = true; + + constructor(private el: ElementRef) {} + + ngOnInit() { + if (this.focus && this.shouldRequestFocus) { + // Otherwise Angular throws error: Expression has changed after it was checked. + window.setTimeout(() => { + this.el.nativeElement.focus(); + }); + } + } + + @Input() set autoFocus(condition: boolean) { + this.focus = condition !== false; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.html new file mode 100644 index 0000000..cf4ce87 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.html @@ -0,0 +1,14 @@ + + + +

PATRIOT_ACT.HEADER

+ + +
+ +

PATRIOT_ACT.BODY.PART1

+

PATRIOT_ACT.BODY.PART2

+

PATRIOT_ACT.BODY.PART3

+
+
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.spec.ts new file mode 100644 index 0000000..71671d6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PatriotActComponent } from './patriot-act.component'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { configureTestSuite } from 'ng-bullet'; + +describe('Patriot Act Component', () => { + let component: PatriotActComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ NgbModule.forRoot()], + declarations: [ PatriotActComponent ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PatriotActComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.ts new file mode 100644 index 0000000..f5c755b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/patriot-act/patriot-act.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { NgbPanelChangeEvent } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'lw-patriot-act', + templateUrl: './patriot-act.component.html', + styleUrls: ['./patriot-act.component.scss'] +}) +export class PatriotActComponent { + public panelOpen = true; + constructor() { } + public beforeChange($event: NgbPanelChangeEvent) { + if ($event.panelId === 'patriot-act-panel') { + this.panelOpen = $event.nextState; + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/address-status.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/address-status.pipe.ts new file mode 100644 index 0000000..9ca7884 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/address-status.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'addressStatus'}) +export class AddressStatusPipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(addressStatus): string { + return this.formatService.formatAddressStatus(addressStatus); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/boolean-string.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/boolean-string.pipe.ts new file mode 100644 index 0000000..4554c0b --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/boolean-string.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'booleanString'}) +export class BooleanStringPipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(booleanString: string): string { + return this.formatService.formatBooleanString(booleanString); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/employment-type.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/employment-type.pipe.ts new file mode 100644 index 0000000..151e4d2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/employment-type.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'employmentType'}) +export class EmploymentTypePipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(employmentType: string): string { + return this.formatService.formatEmploymentType(employmentType); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/format.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/format.service.spec.ts new file mode 100644 index 0000000..30d35ec --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/format.service.spec.ts @@ -0,0 +1,239 @@ +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { FormatService } from './format.service'; +import { SsnPipe } from './ssn.pipe'; +import { PhoneNumberPipe } from './phone-number.pipe'; +import { PhoneTypePipe } from './phone-type.pipe'; +import { AddressStatusPipe } from './address-status.pipe'; +import { EmploymentTypePipe } from './employment-type.pipe'; +import { configureTestSuite } from 'ng-bullet'; +import { LoanCode } from '../loan-type/loan-code'; +import { VehicleOptions } from '../options/vehicle-options'; + + +describe('PipeFormatService', () => { + + let service: FormatService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + SsnPipe, + PhoneNumberPipe, + PhoneTypePipe, + AddressStatusPipe, + EmploymentTypePipe + ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + beforeEach(() => { + service = TestBed.get(FormatService); + }); + + it('should format ssn number properly', () => { + const formatted = service.formatSsn('111224444'); + expect(formatted).toBe('4444'); + }); + + it('should not fail when formatting invalid ssn number', () => { + let formatted = service.formatSsn('1132'); + expect(formatted).toBe(''); + formatted = service.formatSsn(null); + expect(formatted).toBe(''); + }); + + it('should format phone number properly', () => { + const formatted = service.formatPhoneNumber('1113219876'); + expect(formatted).toBe('(111) 321-9876'); + }); + + it('should not fail when formatting invalid phone number', () => { + const formatted = service.formatPhoneNumber(null); + expect(formatted).toBe(''); + }); + + it('should format phone type properly', () => { + let formatted = service.formatPhoneType('0'); + expect(formatted).toBe('Home'); + formatted = service.formatPhoneType('1'); + expect(formatted).toBe('Mobile'); + formatted = service.formatPhoneType(null); + expect(formatted).not.toBe('Home'); + formatted = service.formatPhoneType(0); + expect(formatted).toBe('Home'); + }); + + it('should format address status', () => { + let formatted = service.formatAddressStatus('0'); + expect(formatted).toBe('Own'); + formatted = service.formatAddressStatus('1'); + expect(formatted).toBe('Rent'); + formatted = service.formatAddressStatus('2'); + expect(formatted).toBe('Other'); + formatted = service.formatAddressStatus(null); + expect(formatted).not.toBe('Own'); + formatted = service.formatAddressStatus(0); + expect(formatted).toBe('Own'); + }); + + it('should format employment type', () => { + let formatted = service.formatEmploymentType('FullTime'); + expect(formatted).toBe('Full time'); + formatted = service.formatEmploymentType('PartTime'); + expect(formatted).toBe('Part time'); + formatted = service.formatEmploymentType('Contract'); + expect(formatted).toBe('Contract'); + formatted = service.formatEmploymentType(null); + expect(formatted).not.toBe('Part time'); + }); + + it('should format occupancy type', () => { + let formatted = service.formatOccupancyType('OwnerOccupied'); + expect(formatted).toBe('Owner Occupied'); + formatted = service.formatOccupancyType('VacationHome'); + expect(formatted).toBe('Vacation Home'); + formatted = service.formatOccupancyType('Rental'); + expect(formatted).toBe('Rental'); + formatted = service.formatOccupancyType(null); + expect(formatted).not.toBe('Owner Occupied'); + }); + + it('should format a boolean string', () => { + let formatted = service.formatBooleanString('true'); + expect(formatted).toBe('Yes'); + formatted = service.formatBooleanString('false'); + expect(formatted).toBe('No'); + formatted = service.formatBooleanString(null); + expect(formatted).not.toBe('No'); + }); + + it('should format property type', () => { + let formatted = service.formatPropertyType('SingleFamily'); + expect(formatted).toBe('Single Family Home'); + formatted = service.formatPropertyType('Condo'); + expect(formatted).toBe('Condo'); + formatted = service.formatPropertyType('Family2_4'); + expect(formatted).toBe('Multi Family 2-4'); + formatted = service.formatPropertyType(null); + expect(formatted).not.toBe('Single Family Home'); + }); + + it('should format Manufactured Home property type', () => { + let formatted = service.formatMfgHomePropertyType('Single'); + expect(formatted).toBe('Single'); + formatted = service.formatMfgHomePropertyType('Double'); + expect(formatted).toBe('Double'); + formatted = service.formatMfgHomePropertyType('Triple'); + expect(formatted).toBe('Triple'); + formatted = service.formatMfgHomePropertyType('Quad'); + expect(formatted).toBe('Quad'); + formatted = service.formatMfgHomePropertyType(null); + expect(formatted).not.toBe('Single Family Home'); + }); + + it('should format vehicle seller', () => { + let formatted = service.formatVehicleSeller('Dealer'); + expect(formatted).toBe('Dealership'); + formatted = service.formatVehicleSeller('Private_Party'); + expect(formatted).toBe('Private Party'); + formatted = service.formatVehicleSeller(null); + expect(formatted).not.toBe('Dealership'); + }); + + it('should format state', () => { + let formatted = service.formatState('WI'); + expect(formatted).toBe('Wisconsin'); + formatted = service.formatState('FF'); + expect(formatted).toBe('FF'); + formatted = service.formatState(null); + expect(formatted).not.toBe(''); + }); + + it('should format vehicle type', () => { + const formatted = service.formatVehicleType('PWC'); + expect(formatted).toBe(VehicleOptions.pwcTypeOptions[2].desc); + const formatted2 = service.formatVehicleType('Motorcycle'); + expect(formatted2).toBe(VehicleOptions.vehicleTypeOptions[1].desc); + }); + + it('should format currency', () => { + let formatted = service.formatCurrency(5000); + expect(formatted).toBe('$5,000.00'); + formatted = service.formatCurrency('5000.89'); + expect(formatted).toBe('$5,000.89'); + formatted = service.formatCurrency('$5,000.89'); + expect(formatted).toBe('$5,000.89'); + }); + + it('should format currency', () => { + let formatted = service.formatCurrencyNoDecimal(5000); + expect(formatted).toBe('$5,000'); + formatted = service.formatCurrencyNoDecimal('5000.89'); + expect(formatted).toBe('$5,000'); + formatted = service.formatCurrencyNoDecimal('$5,000.89'); + expect(formatted).toBe('$5,000'); + }); + + it('should format loanCode', () => { + let formatted = service.formatLoanCode(LoanCode.BOAT); + expect(formatted).toBe('Boat'); + formatted = service.formatLoanCode(LoanCode.MANUFACTURED_HOME_LOAN); + expect(formatted).toBe('Manufactured Home'); + formatted = service.formatLoanCode(LoanCode.CC_Student); + expect(formatted).toBe('CC Student'); + formatted = service.formatLoanCode(LoanCode.CC_CashBack); + expect(formatted).toBe('CC CashBack'); + formatted = service.formatLoanCode(LoanCode.CC_Rewards); + expect(formatted).toBe('CC Rewards'); + formatted = service.formatLoanCode(LoanCode.CC_Secure); + expect(formatted).toBe('CC Secure'); + formatted = service.formatLoanCode(LoanCode.EQ_HELOC_Flex); + expect(formatted).toBe('HELOC Flex'); + formatted = service.formatLoanCode(LoanCode.EQ_HELOC_Fixed); + expect(formatted).toBe('Home Equity Fixed'); + formatted = service.formatLoanCode(LoanCode.MTG_REFI_Loan); + expect(formatted).toBe('Mtg Refi Express'); + formatted = service.formatLoanCode(LoanCode.VEHICLE_DEFAULT); + expect(formatted).toBe('Vehicle'); + formatted = service.formatLoanCode(LoanCode.VEHICLE_PRE_APPROVAL); + expect(formatted).toBe('Vehicle Preapproval'); + formatted = service.formatLoanCode(LoanCode.POWERSPORT); + expect(formatted).toBe('Powersport'); + formatted = service.formatLoanCode(LoanCode.TRAILER_MOTORHOME); + expect(formatted).toBe('Trailer Motorhome'); + formatted = service.formatLoanCode(LoanCode.SIGNATURE); + expect(formatted).toBe('Signature'); + formatted = service.formatLoanCode(LoanCode.SPECIAL); + expect(formatted).toBe('Special'); + }); + + it('should format loanCode to icon', () => { + let formatted = service.formatLoanCodeToIcon(LoanCode.BOAT); + expect(formatted).toBe('fa fa-ship'); + formatted = service.formatLoanCodeToIcon(LoanCode.CC_Student); + expect(formatted).toBe('fa fa-credit-card'); + formatted = service.formatLoanCodeToIcon(LoanCode.EQ_HELOC_Flex); + expect(formatted).toBe('fa fa-home'); + }); + + it('should format string in title case', () => { + let formatted = service.formatTitleCase('Matt Millan'); + expect(formatted).toBe('Matt Millan'); + formatted = service.formatTitleCase('MATT MILLAN'); + expect(formatted).toBe('Matt Millan'); + formatted = service.formatTitleCase('mATT'); + expect(formatted).toBe('Matt'); + formatted = service.formatTitleCase('MaTTeRs'); + expect(formatted).toBe('Matters'); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/format.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/format.service.ts new file mode 100644 index 0000000..3cf1202 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/format.service.ts @@ -0,0 +1,276 @@ +import { Injectable } from '@angular/core'; +import { CreditApplicationValues } from '../credit-application-values'; +import { DataTableService } from '../../shared/data-table.service'; +import { LoanCode } from '../loan-type/loan-code'; +import { VehicleOptions } from '../options/vehicle-options'; +import { LoanTypeSet } from '../loan-type'; +import { SelectOptions} from '../options/select-options'; + +@Injectable({ + providedIn: 'root' +}) +export class FormatService { + + constructor(private dataService: DataTableService) { + } + + public formatCurrency(moneyAmt: number | string): string { + if (typeof moneyAmt === 'string') { + moneyAmt = parseFloat(moneyAmt.replace(/[^0-9.]/g, '')); + } + return moneyAmt.toLocaleString('en-US', {style: 'currency', currency: 'USD'}); + } + + public formatCurrencyNoDecimal(moneyAmt: number | string): string { + if (typeof moneyAmt === 'string') { + moneyAmt = parseFloat(moneyAmt.replace(/[^0-9.]/g, '')); + } + return moneyAmt.toLocaleString('en-US', {style: 'currency', currency: 'USD'}).split('.')[0]; + } + + public formatSsn(ssn: string): string { + if (ssn && ssn.length === 9) { + return ssn.substring(5); + } else if (ssn && ssn.length > 8) { + return '*** - ** - ' + ssn.substring(ssn.length - 4); + } else { + return ''; + } + } + + public formatSsnNoStars(ssn: string): string { + if (ssn && ssn.length === 9) { + return ssn.substring(0, 3) + '-' + ssn.substring(3, 5) + '-' + ssn.substring(5); + } else { + return ''; + } + } + + public formatDob(dob: string): string { + if (dob && dob.length > 10) { + // get year + const year = dob.substring(0, 4); + const month = dob.substring(5, 7); + const day = dob.substring(8, 10); + return month + '/' + day + '/' + year; + } else { + return ''; + } + } + + + public formatPhoneNumber(number: string): string { + if (number && number.length === 10) { + return '(' + number.substring(0, 3) + ') ' + number.substring(3, 6) + '-' + number.substring(6); + } else { + return ''; + } + } + + public formatTitleCase(name: string): string { + return name.toLowerCase().split(' ').map(word => + word.charAt(0).toUpperCase() + word.slice(1)).join(' '); + } + + public formatPhoneType(type: any): string { + if (type !== null) { + switch (parseInt(type, 10)) { + case 0: { + return 'Home'; + } + case 1: { + return 'Mobile'; + } + default: { + return 'Phone type not provided'; + } + } + } + } + + public formatAddressStatus(status: any): string { + if (status !== null) { + switch (parseInt(status, 10)) { + case 0: { + return 'Own'; + } + case 1: { + return 'Rent'; + } + case 2: { + return 'Other'; + } + default: { + return 'No address status provided'; + } + } + } + } + + public formatEmploymentType(type: any): string { + if (type !== null) { + switch (type) { + case 'FullTime': { + return 'Full time'; + } + case 'PartTime': { + return 'Part time'; + } + case 'Contract': { + return 'Contract'; + } + default: { + return 'No Employment type provided'; + } + } + } + } + + public formatOccupancyType(type: string): string { + if (type !== null) { + switch (type) { + case 'OwnerOccupied': { + return 'Owner Occupied'; + } + case 'SecondHome': { + return 'Second Home'; + } + case 'Rental': { + return 'Rental'; + } + case 'VacationHome': { + return 'Vacation Home'; + } + case 'Vacant': { + return 'Vacant'; + } + default: { + return 'No Occupancy type provided'; + } + } + } + } + + public formatBooleanString(type: string): string { + if (type !== null) { + switch (type) { + case 'false': { + return 'No'; + } + case 'true': { + return 'Yes'; + } + case CreditApplicationValues.CHOICE_YES: { + return 'Yes'; + } + case CreditApplicationValues.CHOICE_NO: { + return 'No'; + } + default: { + return 'Not a boolean string'; + } + } + } + } + + public formatPropertyType(type: string): string { + if (type !== null) { + switch (type) { + case 'SingleFamily': { + return 'Single Family Home'; + } + case 'Condo': { + return 'Condo'; + } + case 'Townhouse': { + return 'Duplex'; + } + case 'Family2_4': { + return 'Multi Family 2-4'; + } + case 'Other': { + return 'Other'; + } + default: { + return 'Not a Property type'; + } + } + } + } + + public formatMfgHomePropertyType(type: string): string { + if (type !== null) { + switch (type) { + case 'Single': { + return 'Single'; + } + case 'Double': { + return 'Double'; + } + case 'Triple': { + return 'Triple'; + } + case 'Quad': { + return 'Quad'; + } + default: { + return 'Not a Manufactured Home Property type'; + } + } + } + } + + public formatVehicleSeller(type: string): string { + if (type) { + switch (type) { + case 'Private_Party': { + return 'Private Party'; + } + case 'Dealer': { + return 'Dealership'; + } + default: { + return 'Not a Vehicle Seller'; + } + } + } + } + + public formatVehicleType(type: string): string { + let typeDesc = '!Not Found!'; + VehicleOptions.getAllVehicleTypes().forEach( + (vehicleType) => { + if (vehicleType.value === type) { + typeDesc = vehicleType.desc; + } + }, this); + return typeDesc; + } + + public formatState(type: string): string { + if (type) { + const stateName = this.dataService.getStateList()[type]; + if (stateName) { + return stateName; + } else { + return type; + } + } + } + + public formatLoanCode(loanCode: LoanCode): string { + return SelectOptions.loanOptions.find(x => x.value == loanCode).desc + } + + public formatLoanCodeToIcon(loanCode: LoanCode): string { + return LoanTypeSet.find(x => x.apiCode === loanCode).iconClass; + } + + public formatUploadedFiles(files: any[]): string { + let fileNames: string[] = []; + for(let file of files) { + fileNames.push(file.name); + } + return fileNames.join(", "); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/loan-code.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/loan-code.pipe.ts new file mode 100644 index 0000000..f9a61a8 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/loan-code.pipe.ts @@ -0,0 +1,12 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; +import { LoanCode } from '../loan-type/loan-code'; + +@Pipe({name: 'loanCode'}) +export class LoanCodePipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(loanCode: LoanCode): string { + return this.formatService.formatLoanCode(loanCode); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/loan-icon.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/loan-icon.pipe.ts new file mode 100644 index 0000000..2ac330a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/loan-icon.pipe.ts @@ -0,0 +1,12 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; +import { LoanCode } from '../loan-type/loan-code'; + +@Pipe({name: 'loanIcon'}) +export class LoanIconPipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(loanCode: LoanCode): string { + return this.formatService.formatLoanCodeToIcon(loanCode); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/occupancy-type.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/occupancy-type.pipe.ts new file mode 100644 index 0000000..261a12c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/occupancy-type.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'occupancyType'}) +export class OccupancyTypePipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(occupancyType): string { + return this.formatService.formatOccupancyType(occupancyType); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/phone-number.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/phone-number.pipe.ts new file mode 100644 index 0000000..a83d2cb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/phone-number.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'phoneNumber'}) +export class PhoneNumberPipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(phoneNumber: string): string { + return this.formatService.formatPhoneNumber(phoneNumber); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/phone-type.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/phone-type.pipe.ts new file mode 100644 index 0000000..489259a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/phone-type.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'phoneType'}) +export class PhoneTypePipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(phoneType): string { + return this.formatService.formatPhoneType(phoneType); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/property-type.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/property-type.pipe.ts new file mode 100644 index 0000000..015a272 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/property-type.pipe.ts @@ -0,0 +1,20 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'propertyType'}) +export class PropertyTypePipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(propertyType): string { + return this.formatService.formatPropertyType(propertyType); + } +} + +@Pipe({name: 'mfgHomePropertyType'}) +export class MfgHomePropertyTypePipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(mfgHomePropertyType): string { + return this.formatService.formatMfgHomePropertyType(mfgHomePropertyType); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/ssn.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/ssn.pipe.ts new file mode 100644 index 0000000..dce23ec --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/ssn.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'ssn'}) +export class SsnPipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(ssn: string): string { + return this.formatService.formatSsn(ssn); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/state.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/state.pipe.ts new file mode 100644 index 0000000..ba9b573 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/state.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'state'}) +export class StatePipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(state: string): string { + return this.formatService.formatState(state); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/uploaded-files.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/uploaded-files.pipe.ts new file mode 100644 index 0000000..4d56d63 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/uploaded-files.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'uploadedFiles'}) +export class UploadedFilesPipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(uploadedFiles): string { + return this.formatService.formatUploadedFiles(uploadedFiles); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/vehicle-collateral.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/vehicle-collateral.pipe.ts new file mode 100644 index 0000000..7465cc7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/vehicle-collateral.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'vehicleSeller'}) +export class VehicleSellerPipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(seller: string): string { + return this.formatService.formatVehicleSeller(seller); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/vehicle-type.pipe.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/vehicle-type.pipe.ts new file mode 100644 index 0000000..e040cec --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/pipes/vehicle-type.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from '@angular/core'; +import {FormatService} from './format.service'; + +@Pipe({name: 'vehicleType'}) +export class VehicleTypePipe implements PipeTransform { + constructor(private formatService: FormatService) {} + + transform(vehicleType: string): string { + return this.formatService.formatVehicleType(vehicleType); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.html new file mode 100644 index 0000000..462db1f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.html @@ -0,0 +1,1196 @@ +
+
+
+
+
+

+ Cash Back + VISA - Terms and Conditions +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Interest Rates and Interest Charges +

+
+

+ Annual Percentage Rate (APR) for Purchases +

+
+ {{boxInfoCashBack.aprInitial}} Introductory APR for 12 months from account opening. +
After that your APR will be {{boxInfoCashBack.aprPurchase}}
based on your creditworthiness. This APR will vary with the market based on the Prime Rate. +
+

+ APR for Balance Transfers +

+
+ {{boxInfoCashBack.aprInitial}} Introductory APR for 12 months from account opening. +
After that your APR will be {{boxInfoCashBack.aprPurchase}}
based on your creditworthiness. This APR will vary with the market based on the Prime Rate. +
+

+ APR for Cash Advances +

+
+

+ {{boxInfoCashBack.aprCashAdvance}}
+

+
+

+ Penalty APR and When It Applies +

+
+

+ {{boxInfoCashBack.aprPenalty}}.
+
This APR may be applied to your account if you:
+ - Make a late payment.
+

+

How Long Will the Penalty APR Apply? If your APRs are increased for this reason, the Penalty APR will apply until you make + three consecutive minimum payments when due.

+
+

+ How to Avoid Paying Interest on Purchases +

+
+

Your due date is at least 25 days after close of each billing cycle. We will not charge you any interest + on purchases if you pay your entire balance by the due date each month.

+
+

+ Minimum Interest Charge +

+
If you are charged interest, the charge will be no less than {{boxInfoCashBack.minInterestCharge}}. +
+

+ For Credit Card Tips from the Consumer Financial Protection Bureau +

+
+

To learn more about factors to consider when applying for or using a credit card, visit the website + of the Consumer Financial Protection Bureau at + http://www.consumerfinance.gov/learnmore.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Fees +

+
+

+ Annual Fee +

+
+

+ None +

+
+

+ Transaction Fees +

+
+
+
    +
  • Balance Transfer Fees
  • +
+
+

+ {{boxInfoCashBack.introBalTransferFeePct}} of the amount of each balance transfer + during the first 60 days following the opening of Your Account. After that, You will be charged a fee of + {{boxInfoCashBack.transBalTransFeeAmt}} or {{boxInfoCashBack.transBalTransFeePct}} + of the amount of the each balance transfer, whichever is greater +

+
+
    +
  • Cash Advance Fee
  • +
+
+

+ {{boxInfoCashBack.transCashAdvFeeAmt}} or {{boxInfoRewards.transCashAdvFeePct}} + of the amount of the each cash advance, whichever is greater +

+
+
    +
  • Foreign Transaction Fee
  • +
+
+

+ {{boxInfoCashBack.transFgnTransFeePct}} + of each transaction in U.S. dollars.

+
+

+ Penalty Fees +

+
+
+
    +
  • Late Payment Fee
  • +
+
+

+ Up to {{boxInfoCashBack.penLatePaymentFee}} +

+
+
    +
  • Over-the-Credit Limit Fee
  • +
+
+

+ {{boxInfoCashBack.penOverCreditLimitFee}} +

+
+
    +
  • Returned Payment Fee
  • +
+
+

+ Up To {{boxInfoCashBack.penRtnPmtFee}} +

+
+

+ How We Will Calculate Your Balance: We use a method called “average daily balance (including + new purchases).”

+

+ Promotional Period for Introductory APR: The Introductory APR for purchases will apply to transactions posted to Your + Account during the first 12 months following the opening of Your Account. The Introductory APR for balance transfers will apply during the first 12 months following the + opening of Your Account. Balance transfers completed within the first 60 days of opening Your Account, will qualify for {{boxInfoCashBack.introBalTransferFeePct}} + balance transfer fee and Introductory APR. Any existing balances on Landmark Credit Union loan or credit card accounts + are not eligible for the Introductory APR for balance transfers. +

+

+ Loss of Introductory APR: We may end your Introductory APR for purchases and balance transfers and apply the + prevailing non-introductory APR if you are 60 days late in making a payment. +

+

+ Application of Penalty APR:  Your APR may be increased to the disclosed Penalty APR if You are 60 days late in + making a payment. If a minimum payment or any portion thereof is past due by more than 60 days, Your APR will + increase to the Penalty APR as disclosed in the table under “Penalty APR and When it Applies.” This Penalty APR will be + effective following any notice and waiting period required by law. The Penalty APR will remain on Your Account until Your + Account has been paid on time as disclosed in the “Penalty APR and When it Applies” section and with no other event of + default. +

+

+ Minimum Interest Charge: The minimum interest charge will be charged on any dollar amount. +

+

+ Effective Date: The information about the costs of the card described in this application is accurate as of: {{creditCardSharedInformation.cashBackDateUpdated}} + This information may have changed after that date. To find out what may have changed, contact the Credit Union. +

+

+ For California Borrowers, the Visa® Signature Cash Back is a secured credit card. Credit extended under this + credit card account is secured by various personal property and money including, but not limited to: (a) any + goods you purchase with this account, (b) any shares you specifically pledge as collateral for this account on a + separate Pledge of Shares, (c) all shares you have in any individual or joint account with the Credit Union + excluding shares in an Individual Retirement Account or in any other account that would lose special tax + treatment under state or federal law, and (d) collateral securing other loans you have with the Credit Union + excluding dwellings. +

+

+ Notice to New York Residents: New York residents may contact the New York State Department of Financial Services + to obtain a comparative listing of credit card rates, fees, and grace periods. The New York State Department of Financial + Services may be contacted at 1-800-342-3736 or www.dfs.ny.gov  +

+

+ Other Fees & Disclosures: 
+ Late Payment Fee: {{boxInfoCashBack.latePmtFee}} or the amount of the required minimum payment, whichever is less, if you are one or more + days late in making a payment. +
+
+ Balance Transfer Fee (Finance Charge): {{boxInfoCashBack.introBalTransferFeePct}} of the amount of each balance transfer during the first 60 days following + the opening of Your Account. After that, You will be charged a fee of {{boxInfoCashBack.balTransferFeeAmt}} or {{boxInfoCashBack.balTransferFeePct}} of the amount of each balance + transfer, whichever is greater. +
+
+ Cash Advance Fee (Finance Charge): {{boxInfoCashBack.cashAdvanceFeeAmt}} or {{boxInfoCashBack.cashAdvanceFeePct}} of the amount of each cash advance, whichever is greater. +
+
+ Returned Payment Fee: {{boxInfoCashBack.returnedPmtFee}} or the amount of the required minimum payment, whichever is less. +
+
+ Returned Convenience Check Fee: {{boxInfoCashBack.returnedConvenienceCheckFee}} or the amount of the returned convenience check, whichever is less. +
+
+ Card Replacement Fee: {{boxInfoCashBack.cardReplacementFee}}. +
+
+ Pay-by-Phone Fee: {{boxInfoCashBack.payByPhoneFee}}. +
+
+ Statement Copy Fee: {{boxInfoCashBack.statementCopyFee}}. +
+
+ Rush Card Fee: {{boxInfoCashBack.rushCardFee}}. +
+

+
+
+
+
+

+ Rewards + VISA - Terms and Conditions +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Interest Rates and Interest Charges +

+
+

+ Annual Percentage Rate (APR) for Purchases +

+
+ {{boxInfoRewards.aprInitial}} Introductory APR for 12 months from account opening. +
After that your APR will be {{boxInfoRewards.aprPurchase}}
based on your creditworthiness. This APR will vary with the market based on the Prime Rate. +
+

+ APR for Balance Transfers +

+
+ {{boxInfoRewards.aprInitial}} Introductory APR for 12 months from account opening. +
After that your APR will be {{boxInfoRewards.aprPurchase}}
based on your creditworthiness. This APR will vary with the market based on the Prime Rate. +
+

+ APR for Cash Advances +

+
+

+ + {{boxInfoRewards.aprCashAdvance}}
+

+
+

+ Penalty APR and When It Applies +

+ +
+

+ {{boxInfoRewards.aprPenalty}}.
+
This APR may be applied to your account if you:
+ - Make a late payment.
+

+

How Long Will the Penalty APR Apply? If your APRs are increased for this reason, the Penalty APR will apply until you make + three consecutive minimum payments when due.

+
+

+ How to Avoid Paying Interest on Purchases +

+
+

Your due date is at least 25 days after close of each billing cycle. We will not charge you any interest + on purchases if you pay your entire balance by the due date each month.

+
+

+ Minimum Interest Charge +

+
If you are charged interest, the charge will be no less than {{boxInfoRewards.minInterestCharge}}. +
+

+ For Credit Card Tips from the Consumer Financial Protection Bureau +

+
+

To learn more about factors to consider when applying for or using a credit card, visit the website + of the Consumer Financial Protection Bureau at + http://www.consumerfinance.gov/learnmore.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Fees +

+
+

+ Annual Fee +

+
+

+ None +

+
+

+ Transaction Fees +

+
+
+
    +
  • Balance Transfer Fees
  • +
+
+

+ {{boxInfoRewards.introBalTransferFeePct}} of the amount of each balance transfer + during the first 60 days following the opening of Your Account. After that, You will be charged a fee of + {{boxInfoRewards.transBalTransFeeAmt}} or {{boxInfoRewards.transBalTransFeePct}} + of the amount of the each balance transfer, whichever is greater +

+
+
    +
  • Cash Advance Fee
  • +
+
+

+ {{boxInfoRewards.transCashAdvFeeAmt}} or {{boxInfoRewards.transCashAdvFeePct}} + of the amount of the each cash advance, whichever is greater +

+
+
    +
  • Foreign Transaction Fee
  • +
+
+

+ {{boxInfoRewards.transFgnTransFeePct}} + of each transaction in U.S. dollars.

+
+

+ Penalty Fees +

+
+
+
    +
  • Late Payment Fee
  • +
+
+

+ Up to {{boxInfoRewards.penLatePaymentFee}} +

+
+
    +
  • Over-the-Credit Limit Fee
  • +
+
+

+ {{boxInfoRewards.penOverCreditLimitFee}} +

+
+
    +
  • Returned Payment Fee
  • +
+
+

+ Up To {{boxInfoRewards.penRtnPmtFee}} +

+
+ +
+

+ How We Will Calculate Your Balance: We use a method called “average daily balance (including + new purchases).”

+

+ Promotional Period for Introductory APR: The Introductory APR for purchases will apply to transactions posted to Your + Account during the first 12 months following the opening of Your Account. The Introductory APR for balance transfers will apply during the first 12 months following the + opening of Your Account. Balance transfers completed within the first 60 days of opening Your Account, will qualify for {{boxInfoRewards.introBalTransferFeePct}} + balance transfer fee and Introductory APR. Any existing balances on Landmark Credit Union loan or credit card accounts + are not eligible for the Introductory APR for balance transfers. +

+

+ Loss of Introductory APR: We may end your Introductory APR for purchases and balance transfers and apply the + prevailing non-introductory APR if you are 60 days late in making a payment. +

+

+ Application of Penalty APR: Your APR may be increased to the disclosed Penalty APR if you are 60 days late in making + a payment. If a minimum payment or any portion thereof is past due by more than 60 days, Your APR will increase to the + Penalty APR as disclosed in the table under “Penalty APR and When it Applies.” This Penalty APR will be effective + following any notice and waiting period required by law. The Penalty APR will remain on Your Account until Your Account + has been paid on time as disclosed in the “Penalty APR and When it Applies” section and with no other event of default. +

+

+ Minimum Interest Charge: The minimum interest charge will be charged on any dollar amount. +

+

+ Effective Date: The information about the costs of the card described in this application is accurate as of: {{creditCardSharedInformation.rewardsDateUpdated}} + This information may have changed after that date. To find out what may have changed, contact the Credit Union. +

+

+ For California Borrowers, the Rewards Visa® is a secured credit card. Credit extended under this credit card + account is secured by various personal property and money including, but not limited to: (a) any goods you + purchase with this account, (b) any shares you specifically pledge as collateral for this account on a separate + Pledge of Shares, (c) all shares you have in any individual or joint account with the Credit Union excluding + shares in an Individual Retirement Account or in any other account that would lose special tax treatment under + state or federal law, and (d) collateral securing other loans you have with the Credit Union excluding dwellings. +

+

+ Notice to New York Residents: New York residents may contact the New York State Department of Financial Services + to obtain a comparative listing of credit card rates, fees, and grace periods. The New York State Department of Financial + Services may be contacted at 1-800-342-3736 or www.dfs.ny.gov  +

+

+ Other Fees & Disclosures: 
+ Late Payment Fee: {{boxInfoRewards.latePmtFee}} or the amount of the required minimum payment, whichever is less, if you are one or more + days late in making a payment. +
+
+ Balance Transfer Fee (Finance Charge): {{boxInfoRewards.introBalTransferFeePct}} of the amount of each balance transfer during the first 60 days following + the opening of Your Account. After that, You will be charged a fee of {{boxInfoRewards.balTransferFeeAmt}} or {{boxInfoRewards.balTransferFeePct}} of the amount of each balance + transfer, whichever is greater. +
+
+ Cash Advance Fee (Finance Charge): {{boxInfoRewards.cashAdvanceFeeAmt}} or {{boxInfoRewards.cashAdvanceFeePct}} of the amount of each cash advance, whichever is greater. +
+
+ Returned Payment Fee: {{boxInfoRewards.returnedPmtFee}} or the amount of the required minimum payment, whichever is less. +
+
+ Returned Convenience Check Fee: {{boxInfoRewards.returnedConvenienceCheckFee}} or the amount of the returned convenience check, whichever is less. +
+
+ Card Replacement Fee: {{boxInfoRewards.cardReplacementFee}}. +
+
+ Pay-by-Phone Fee: {{boxInfoRewards.payByPhoneFee}}. +
+
+ Statement Copy Fee: {{boxInfoRewards.statementCopyFee}}. +
+
+ Rush Card Fee: {{boxInfoRewards.rushCardFee}}. +
+

+
+
+
+
+

+ Secured + VISA - Terms and Conditions +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Interest Rates and Interest Charges +

+
+

+ Annual Percentage Rate (APR) for Purchases +

+
+ {{boxInfoSecured.aprPurchase}} +
+
+

+ APR for Balance Transfers +

+
+

+ {{boxInfoSecured.aprTransfer}} +

+
+

+ APR for Cash Advances +

+
+

+ {{boxInfoSecured.aprCashAdvance}} +

+
+

+ Penalty APR and When It Applies +

+
+

+ {{boxInfoSecured.aprPenalty}}
+
This APR may be applied to your account if you:
+ - Make a late payment.
+

+

How Long Will the Penalty APR Apply? If your APRs are increased for this reason, the Penalty APR will apply until you make + three consecutive minimum payments when due.

+
+

+ How to Avoid Paying Interest on Purchases +

+
+

Your due date is at least 25 days after close of each billing cycle. We will not charge you any interest + on purchases if you pay your entire balance by the due date each month.

+
+

+ Minimum Interest Charge +

+
+

If you are charged interest, the charge will be no less than {{boxInfoSecured.minInterestCharge}}.

+
+

+ For Credit Card Tips from the Consumer Financial Protection Bureau +

+
+

To learn more about factors to consider when applying for or using a credit card, visit the website + of the Consumer Financial Protection Bureau at

+ http://www.consumerfinance.gov/learnmore. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Fees +

+
+

+ Annual Fee +

+
+

+ $25.00 +

+
+

+ Transaction Fees +

+
+
+
    +
  • Balance Transfer Fees
  • +
+
+

+ {{boxInfoSecured.transBalTransFeeAmt}} or {{boxInfoSecured.transBalTransFeePct}} + of the amount of the each balance transfer, whichever is greater +

+
+
    +
  • Cash Advance Fee
  • +
+
+

+ {{boxInfoSecured.transCashAdvFeeAmt}} or {{boxInfoSecured.transCashAdvFeePct}} + of the amount of the each cash advance, whichever is greater +

+
+
    +
  • Foreign Transaction Fee
  • +
+
+

+ {{boxInfoSecured.transFgnTransFeePct}} + of each transaction in U.S. dollars.

+
+

+ Penalty Fees +

+
+
+
    +
  • Late Payment Fee
  • +
+
+

+ Up to {{boxInfoSecured.penLatePaymentFee}} +

+
+
    +
  • Over-the-Credit Limit Fee
  • +
+
+

+ {{boxInfoSecured.penOverCreditLimitFee}} +

+
+
    +
  • Returned Payment Fee
  • +
+
+

+ Up To {{boxInfoSecured.penRtnPmtFee}} +

+
+

+ How We Will Calculate Your Balance: We use a method called “average daily balance (including + new purchases).” +

+

+ Application of Penalty APR:  Your APR may be increased to the disclosed Penalty APR if you are 60 days late in making + a payment. If a minimum payment or any portion thereof is past due by more than 60 days, Your APR will increase to the + Penalty APR as disclosed in the table under “Penalty APR and When it Applies.” This Penalty APR will be effective + following any notice and waiting period required by law. The Penalty APR will remain on Your Account until Your Account + has been paid on time as disclosed in the “Penalty APR and When it Applies” section and with no other event of default. +

+

+ Minimum Interest Charge: The minimum interest charge will be charged on any dollar amount. +

+

+ Effective Date: The information about the costs of the card described in this application is accurate as of: {{creditCardSharedInformation.securedDateUpdated}} + This information may have changed after that date. To find out what may have changed, contact the Credit Union. +

+

+ For California Borrowers, the Secured Visa® is a secured credit card. Credit extended under this credit card + account is secured by various personal property and money including, but not limited to: (a) any goods you + purchase with this account, (b) any shares you specifically pledge as collateral for this account on a separate + Pledge of Shares. + +

+

+ Notice to New York Residents: New York residents may contact the New York State Department of Financial Services + to obtain a comparative listing of credit card rates, fees, and grace periods. The New York State Department of Financial + Services may be contacted at 1-800-342-3736 or www.dfs.ny.gov  +

+

+ Other Fees & Disclosures: 
+ Late Payment Fee: {{boxInfoSecured.latePmtFee}} or the amount of the required minimum payment, whichever is less, if you are one or more + days late in making a payment. +
+
+ Annual Fee: {{boxInfoSecured.annualFee}}. +
+
+ Balance Transfer Fee (Finance Charge): {{boxInfoSecured.balTransferFeeAmt}} or {{boxInfoSecured.balTransferFeePct}} of the amount of each balance transfer, whichever is greater. +
+
+ Cash Advance Fee (Finance Charge): {{boxInfoSecured.cashAdvanceFeeAmt}} or {{boxInfoSecured.cashAdvanceFeePct}} of the amount of each cash advance, whichever is greater. +
+
+ Returned Payment Fee: {{boxInfoSecured.returnedPmtFee}} or the amount of the required minimum payment, whichever is less. +
+
+ Returned Convenience Check Fee: {{boxInfoSecured.returnedConvenienceCheckFee}} or the amount of the returned convenience check, whichever is less. +
+
+ Card Replacement Fee: {{boxInfoSecured.cardReplacementFee}}. +
+
+ Pay-by-Phone Fee: {{boxInfoSecured.payByPhoneFee}}. +
+
+ Statement Copy Fee: {{boxInfoSecured.statementCopyFee}}. +
+
+ Rush Card Fee: {{boxInfoSecured.rushCardFee}}. +
+

+
+
+
+
+

+ Student + VISA - Terms and Conditions +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Interest Rates and Interest Charges +

+
+

+ Annual Percentage Rate (APR) for Purchases +

+
+ {{boxInfoStudent.aprPurchase}}
+
This APR will vary with the market based on the Prime Rate. +
+

+ APR for Balance Transfers +

+
+ {{boxInfoStudent.aprTransfer}}
+
This APR will vary with the market based on the Prime Rate. +
+

+ APR for Cash Advances +

+
+

+ {{boxInfoStudent.aprCashAdvance}} +

+
+

+ Penalty APR and When It Applies +

+
+

+ {{boxInfoStudent.aprPenalty}}.
+
This APR may be applied to your account if you:
+ - Make a late payment.
+

+

How Long Will the Penalty APR Apply? If your APRs are increased for this reason, the Penalty APR will apply until you make + three consecutive minimum payments when due.

+

+
+

+ How to Avoid Paying Interest on Purchases +

+
+

Your due date is at least 25 days after close of each billing cycle. We will not charge you any interest + on purchases if you pay your entire balance by the due date each month.

+
+

+ Minimum Interest Charge +

+
+

If you are charged interest, the charge will be no less than {{boxInfoStudent.minInterestCharge}}.

+
+

+ For Credit Card Tips from the Consumer Financial Protection Bureau +

+
+

To learn more about factors to consider when applying for or using a credit card, visit the website + of the Consumer Financial Protection Bureau at + http://www.consumerfinance.gov/learnmore.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Fees +

+
+

+ Annual Fee +

+
+

+ None +

+
+

+ Transaction Fees +

+
+
+
    +
  • Balance Transfer Fees
  • +
+
+

+ {{boxInfoStudent.transBalTransFeeAmt}} or {{boxInfoStudent.transBalTransFeePct}} + of the amount of the each balance transfer, whichever is greater +

+
+
    +
  • Cash Advance Fee
  • +
+
+

+ {{boxInfoStudent.transCashAdvFeeAmt}} or {{boxInfoStudent.transCashAdvFeePct}} + of the amount of the each cash advance, whichever is greater +

+
+
    +
  • Foreign Transaction Fee
  • +
+
+

+ {{boxInfoStudent.transFgnTransFeePct}} + of each transaction in U.S. dollars.

+
+

+ Penalty Fees +

+
+
+
    +
  • Late Payment Fee
  • +
+
+

+ Up to {{boxInfoStudent.penLatePaymentFee}} +

+
+
    +
  • Over-the-Credit Limit Fee
  • +
+
+

+ {{boxInfoStudent.penOverCreditLimitFee}} +

+
+
    +
  • Returned Payment Fee
  • +
+
+

+ Up To {{boxInfoStudent.penRtnPmtFee}} +

+
+

+ How We Will Calculate Your Balance: + We use a method called "average daily balance (including new purchases)."

+

+ Application of Penalty APR:  Your APR may be increased to the disclosed Penalty APR if you are 60 days late in making + a payment. If a minimum payment or any portion thereof is past due by more than 60 days, Your APR will increase to the + Penalty APR as disclosed in the table under “Penalty APR and When it Applies.” This Penalty APR will be effective + following any notice and waiting period required by law. The Penalty APR will remain on Your Account until Your Account + has been paid on time as disclosed in the “Penalty APR and When it Applies” section and with no other event of default. +

+

+ Minimum Interest Charge: The minimum interest charge will be charged on any dollar amount. +

+

+ Effective Date: The information about the costs of the card described in this application is accurate as of: {{creditCardSharedInformation.studentDateUpdated}} + This information may have changed after that date. To find out what may have changed, contact the Credit Union. +

+

+ For California Borrowers, the Student Rewards Visa® is a secured credit card. Credit extended under this credit + card account is secured by various personal property and money including, but not limited to: (a) any goods you + purchase with this account, (b) any shares you specifically pledge as collateral for this account on a separate + Pledge of Shares, (c) all shares you have in any individual or joint account with the Credit Union excluding + shares in an Individual Retirement Account or in any other account that would lose special tax treatment under + state or federal law, and (d) collateral securing other loans you have with the Credit Union excluding dwellings. + +

+

+ Notice to New York Residents: New York residents may contact the New York State Department of Financial Services + to obtain a comparative listing of credit card rates, fees, and grace periods. The New York State Department of Financial + Services may be contacted at 1-800-342-3736 or www.dfs.ny.gov  +

+

+ Other Fees & Disclosures: 
+ Late Payment Fee: {{boxInfoStudent.latePmtFee}} or the amount of the required minimum payment, whichever is less, if you are one or more + days late in making a payment. +
+
+ Balance Transfer Fee (Finance Charge): {{boxInfoStudent.balTransferFeeAmt}} or {{boxInfoStudent.balTransferFeePct}} of the amount of each balance transfer, whichever is greater. +
+
+ Cash Advance Fee (Finance Charge): {{boxInfoStudent.cashAdvanceFeeAmt}} or {{boxInfoStudent.cashAdvanceFeePct}} of the amount of each cash advance, whichever is greater. +
+
+ Returned Payment Fee: {{boxInfoStudent.returnedPmtFee}} or the amount of the required minimum payment, whichever is less. +
+
+ Returned Convenience Check Fee: {{boxInfoStudent.returnedConvenienceCheckFee}} or the amount of the returned convenience check, whichever is less. +
+
+ Card Replacement Fee: {{boxInfoStudent.cardReplacementFee}}. +
+
+ Pay-by-Phone Fee: {{boxInfoStudent.payByPhoneFee}}. +
+
+ Statement Copy Fee: {{boxInfoStudent.statementCopyFee}}. +
+
+ Rush Card Fee: {{boxInfoStudent.rushCardFee}}. +
+

+
+
+
+ \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.scss b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.scss new file mode 100644 index 0000000..1d585d5 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.scss @@ -0,0 +1,52 @@ +$max-width: 770px; + +.schumer-text { + font-family: arial, helvetica, sans-serif; + font-size: 11pt; +} + +.schumer-text2 { + font-family: arial, helvetica, sans-serif; + font-size: 11pt; +} + +.schumer-table-td { + border-width: medium 1px; + border-style: none solid; + padding: 3px; + vertical-align: top; + border-collapse: collapse; +} + +.schumer-table-td2 { + border-width: 1px 1px medium; + border-style: solid solid none; + padding: 3px; + vertical-align: top; + border-collapse: collapse; +} + +.schumer-table-td3 { + padding: 3px; + border: 1px solid #000000; + vertical-align: top; + border-collapse: collapse; +} + +.schumer-table { + margin: 0pt 0pt 20px; + border: 2px solid #000000; + width: 100%; + font-family: arial, helvetica, sans-serif; + font-size: 11pt; + border-collapse: collapse; +} + +@media screen and (max-width: $max-width) { + .url-long { + display: none; + } + .url-short[style] { + display: inline !important; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.spec.ts new file mode 100644 index 0000000..042873c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.spec.ts @@ -0,0 +1,40 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { SchumerBoxComponent } from './schumer-box.component'; +import { WindowService } from '../window.service'; +import { configureTestSuite } from 'ng-bullet'; +import { LoanCode } from '../loan-type/loan-code'; + +describe('SchumerBoxComponent', () => { + let component: SchumerBoxComponent; + let fixture: ComponentFixture; + let windowService: WindowService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot() + ], + declarations: [ + SchumerBoxComponent + ], + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SchumerBoxComponent); + component = fixture.componentInstance; + windowService = TestBed.get(WindowService); + }); + + it('should use the window service to print.', () => { + component.boxType = LoanCode.CC_CashBack; + fixture.detectChanges(); + spyOn(windowService, 'showPrintWindowForId').and.returnValue(undefined); + component.commandPrint(); + expect(windowService.showPrintWindowForId).toHaveBeenCalled(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.ts new file mode 100644 index 0000000..fa29599 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/schumer-boxes/schumer-box.component.ts @@ -0,0 +1,147 @@ +import {Component, Input} from '@angular/core'; +import {WindowService} from '../window.service'; + +@Component({ + selector: 'lw-schumer-box', + templateUrl: './schumer-box.component.html', + styleUrls: ['./schumer-box.component.scss'] +}) +export class SchumerBoxComponent { + + // This is the beginning of factoring out some variables/component stuff. + public boxInfoRewards = { + aprInitial: '0.00%', + aprPurchase: '15.74% to 24.74%', + aprTransfer: '15.74% to 24.74%', + aprCashAdvance: '29.99%', + aprPenalty: '29.99%', + minInterestCharge: '$1.00', + transBalTransFeePct: '5.00%', + transBalTransFeeAmt: '$5.00', + transCashAdvFeePct: '3.00%', + transCashAdvFeeAmt: '$5.00', + transFgnTransFeePct: '1.00%', + penLatePaymentFee: '$25.00', + penOverCreditLimitFee: 'None', + penRtnPmtFee: '$30.00', + latePmtFee: '$25.00', + balTransferFeeAmt: '$5.00', + balTransferFeePct: '5.00%', + introBalTransferFeePct: '3.00%', + cashAdvanceFeeAmt: '$5.00', + cashAdvanceFeePct: '3.00%', + returnedPmtFee: '$30.00', + returnedConvenienceCheckFee: '$30.00', + cardReplacementFee: '$6.00', + payByPhoneFee: '$10.00', + statementCopyFee: '$6.00', + rushCardFee: '$45.00' + + }; + + public boxInfoCashBack = { + aprInitial: '0.00%', + aprPurchase: '15.74% to 24.74%', + aprTransfer: '15.74% to 24.74%', + aprCashAdvance: '29.99%', + aprPenalty: '29.99%', + minInterestCharge: '$1.00', + transBalTransFeePct: '5.00%', + transBalTransFeeAmt: '$5.00', + transCashAdvFeePct: '3.00%', + transCashAdvFeeAmt: '$5.00', + transFgnTransFeePct: '1.00%', + introBalTransferFeePct: '3.00%', + penLatePaymentFee: '$25.00', + penOverCreditLimitFee: 'None', + penRtnPmtFee: '$30.00', + latePmtFee: '$25.00', + balTransferFeeAmt: '$5.00', + balTransferFeePct: '5.00%', + cashAdvanceFeeAmt: '$5.00', + cashAdvanceFeePct: '3.00%', + returnedPmtFee: '$30.00', + returnedConvenienceCheckFee: '$30.00', + cardReplacementFee: '$6.00', + payByPhoneFee: '$10.00', + statementCopyFee: '$6.00', + rushCardFee: '$45.00' + }; + + public boxInfoStudent = { + aprInitial: '', + aprPurchase: '19.49%', + aprTransfer: '19.49%', + aprCashAdvance: '29.99%', + aprPenalty: '29.99%', + minInterestCharge: '$1.00', + transBalTransFeePct: '5.00%', + transBalTransFeeAmt: '$5.00', + transCashAdvFeePct: '3.00%', + transCashAdvFeeAmt: '$5.00', + transFgnTransFeePct: '1.00%', + penLatePaymentFee: '$25.00', + penOverCreditLimitFee: 'None', + penRtnPmtFee: '$30.00', + latePmtFee: '$25.00', + balTransferFeeAmt: '$5.00', + balTransferFeePct: '5.00%', + cashAdvanceFeeAmt: '$5.00', + cashAdvanceFeePct: '3.00%', + returnedPmtFee: '$30.00', + returnedConvenienceCheckFee: '$30.00', + cardReplacementFee: '$6.00', + payByPhoneFee: '$10.00', + statementCopyFee: '$6.00', + rushCardFee: '$45.00' + }; + + public boxInfoSecured = { + aprInitial: '', + aprPurchase: '18.99%', + aprTransfer: '18.99%', + aprCashAdvance: '29.99%', + aprPenalty: '29.99%', + minInterestCharge: '$1.00', + transBalTransFeePct: '5.00%', + transBalTransFeeAmt: '$5.00', + transCashAdvFeePct: '3.00%', + transCashAdvFeeAmt: '$5.00', + transFgnTransFeePct: '1.00%', + penLatePaymentFee: '$25.00', + penOverCreditLimitFee: 'None', + penRtnPmtFee: '$30.00', + latePmtFee: '$25.00', + annualFee: '$25.00', + balTransferFeeAmt: '$5.00', + balTransferFeePct: '5.00%', + cashAdvanceFeeAmt: '$5.00', + cashAdvanceFeePct: '3.00%', + returnedPmtFee: '$30.00', + returnedConvenienceCheckFee: '$30.00', + cardReplacementFee: '$6.00', + payByPhoneFee: '$10.00', + statementCopyFee: '$6.00', + rushCardFee: '$45.00' + }; + + public creditCardSharedInformation = { + cashBackDateUpdated: 'February 1, 2026', + rewardsDateUpdated: 'February 1, 2026', + studentDateUpdated: 'February 1, 2026', + securedDateUpdated: 'February 1, 2025' + } + + @Input() + public boxType: string; + + @Input() + public showSchumer: boolean; + + constructor(private windowService: WindowService) { + } + + public commandPrint(): void { + this.windowService.showPrintWindowForId('outer-schumer-box-container'); + } +} \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/security.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/security.service.spec.ts new file mode 100644 index 0000000..02fd1a6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/security.service.spec.ts @@ -0,0 +1,76 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { configureTestSuite } from 'ng-bullet'; +import { SecurityService } from './security.service'; +import { MsAdalAngular6Module } from 'microsoft-adal-angular6'; + +describe('SecurityService', () => { + + let service: SecurityService; + const testAuthorizedTester = { + profile: { + roles: [SecurityService.TESTER_ROLE_ID] + } + } as adal.User; + const testAuthorizedAdmin = { + profile: { + roles: [SecurityService.ADMIN_ROLE_ID] + } + } as adal.User; + const testNotAuthorizeduser = { + profile: { + roles: ['notamatchingid'] + } + } as adal.User; + const testNotValidData = { + profile: undefined + } as adal.User; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + MsAdalAngular6Module.forRoot({ + tenant: '1234567', + clientId: '7654321', + redirectUri: 'http://localhost', + endpoints: {}, + navigateToLoginRequestUrl: false, + cacheLocation: 'localStorage' + }) + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [] + }); + }); + + beforeEach(() => { + service = TestBed.get(SecurityService); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should handle invalid calls to check roll', () => { + expect( service.checkRole(testNotValidData, null) ).toEqual(false); + expect( service.checkRole(testAuthorizedAdmin, null) ).toEqual(false); + }); + + it('should handle invalid users passed to check roll', () => { + expect( service.checkRole(testNotValidData, SecurityService.TESTER_ROLE_ID) ).toEqual(false); + }); + + it('should handle valid calls to check roll', () => { + expect( service.checkRole(testAuthorizedAdmin, SecurityService.TESTER_ROLE_ID) ).toEqual(false); + expect( service.checkRole(testAuthorizedTester, SecurityService.TESTER_ROLE_ID) ).toEqual(true); + expect( service.checkRole(testAuthorizedAdmin, SecurityService.ADMIN_ROLE_ID) ).toEqual(true); + expect( service.checkRole(testNotAuthorizeduser, SecurityService.ADMIN_ROLE_ID) ).toEqual(false); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/security.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/security.service.ts new file mode 100644 index 0000000..ce5a1af --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/security.service.ts @@ -0,0 +1,50 @@ +import { ErrorHandler, Injectable } from '@angular/core'; +import { MsAdalAngular6Service } from 'microsoft-adal-angular6'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class SecurityService { + public static readonly ADMIN_ROLE_ID = 'Admin'; + public static readonly TESTER_ROLE_ID = 'Tester'; + + constructor(private adalSvc: MsAdalAngular6Service, + private eHandler: ErrorHandler) { + } + + public isAuthenticated(): boolean { + return !!(this.adalSvc.isAuthenticated); + } + + public login(): void { + this.adalSvc.login(); + } + + public checkAdminRole(): boolean | Observable { + if (this.adalSvc.userInfo) { + return this.checkRole(this.adalSvc.userInfo, SecurityService.ADMIN_ROLE_ID); + } + return false; + } + + public checkTesterRole(): boolean | Observable { + if (this.adalSvc.userInfo) { + return this.checkRole(this.adalSvc.userInfo, SecurityService.TESTER_ROLE_ID); + } + return false; + } + + public checkRole(userInfo: adal.User, role: String): boolean | Observable { + if (userInfo && userInfo.profile) { + if (userInfo.profile.roles && userInfo.profile.roles.length) { + const roles = userInfo.profile.roles; + return roles.indexOf(role) > -1; + } else { + // @TODO ERROR_NOTIFY + } + } + return false; + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/shared.module.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/shared.module.ts new file mode 100644 index 0000000..a306d85 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/shared.module.ts @@ -0,0 +1,12 @@ +import {LoanCodePipe} from './pipes/loan-code.pipe'; +import {NgModule} from '@angular/core'; +import {SchumerBoxComponent} from './schumer-boxes/schumer-box.component'; +import {CommonModule} from '@angular/common'; + +@NgModule({ + imports: [CommonModule], + declarations: [LoanCodePipe, SchumerBoxComponent], + exports: [LoanCodePipe, SchumerBoxComponent] +}) +export class SharedModule { +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/available-terms-calculator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/available-terms-calculator.ts new file mode 100644 index 0000000..7b9af24 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/available-terms-calculator.ts @@ -0,0 +1,5 @@ +import { TermInputFields } from './term-input-fields'; + +export interface AvailableTermsCalculator { + determineTerm(): Array<{ value: any, desc: string }>; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/default-term-calculator.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/default-term-calculator.spec.ts new file mode 100644 index 0000000..64e6a67 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/default-term-calculator.spec.ts @@ -0,0 +1,59 @@ +import { TermInputFields } from './term-input-fields'; +import { DefaultTermCalculator } from './default-term-calculator'; +import { LoanCode } from '../loan-type/loan-code'; +import { TermOption } from '../configuration/ui-settings'; +import { DefaultSettings } from '../configuration/default-settings.mock'; + +const boatMockOptions = { + 5000: [48], + 10000: [63, 84], + 20000: [120, 180] +}; + +const mockAvailableTermOptions = >DefaultSettings.termOptionsNoYear; + +const mockInputBoat: TermInputFields = new TermInputFields(LoanCode.BOAT, 0, 5000, 0); +const mockInputBoatHighAmount: TermInputFields = new TermInputFields(LoanCode.BOAT, 0, 25000, 0); + +describe('DefaultTermCalculator', () => { + + it('should build a list of term options from an array of numbers', () => { + const calc = new DefaultTermCalculator(mockInputBoat, boatMockOptions, mockAvailableTermOptions); + + const listOfPotentialTermLengths = [36, 48, 63 , 72, 75, 84, 120, 180]; + expect(calc.buildListByFilteringTermOptions(listOfPotentialTermLengths, mockAvailableTermOptions)) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'}, + {value: 72, desc: '72 months'}, + {value: 75, desc: '75 months'}, + {value: 84, desc: '84 months'}, + {value: 120, desc: '120 months'}, + {value: 180, desc: '180 months'}, + ]); + }); + + it('should build a list of boat terms based on 5000 amount', () => { + const calc = new DefaultTermCalculator(mockInputBoat, boatMockOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 48, desc: 'Up to 48 months'}, + ]); + }); + + it('should build a list of boat terms based on 25000 amount', () => { + const calc = new DefaultTermCalculator(mockInputBoatHighAmount, boatMockOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 48, desc: 'Up to 48 months'}, + {value: 63, desc: '63 months'}, + {value: 84, desc: '84 months'}, + {value: 120, desc: '120 months'}, + {value: 180, desc: '180 months'}, + ]); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/default-term-calculator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/default-term-calculator.ts new file mode 100644 index 0000000..110ab8d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/default-term-calculator.ts @@ -0,0 +1,50 @@ +import { AvailableTermsCalculator } from './available-terms-calculator'; +import { TermInputFields } from './term-input-fields'; +import { TermOptions } from 'src/app/shared/options/term-options'; +import { TermOption } from '../configuration/ui-settings'; + +export class DefaultTermCalculator implements AvailableTermsCalculator { + private fields: TermInputFields; + private termMonthsOptions: any; + private availableTermOptions: Array; + + constructor(fields: TermInputFields, termMonthsOptions: any, availableTermOptions: Array) { + this.fields = fields; + this.termMonthsOptions = termMonthsOptions; + this.availableTermOptions = availableTermOptions; + } + + public determineTerm(): Array<{ value: any, desc: string }> { + return this.buildTerms(this.termMonthsOptions, this.fields, this.availableTermOptions); + } + + public buildTerms( + termMonthsOptions: any, fields: TermInputFields, availableTermOptions: Array): Array<{ value: any, desc: string }> { + let uiTermsFromRequestedAmount = []; + + Object.keys(termMonthsOptions).forEach(tier => { + if (parseInt(tier, 10) <= this.fNum(fields.amount)) { + uiTermsFromRequestedAmount = uiTermsFromRequestedAmount.concat(termMonthsOptions[tier]); + } + }); + return this.buildListByFilteringTermOptions(uiTermsFromRequestedAmount, availableTermOptions); + } + + public buildListByFilteringTermOptions( + termMonthsOptions: Array, availableTermOptions: Array): Array<{ value: any, desc: string }> { + const uiSettingsKeyTermOptionsWithoutYears = TermOptions.uiSettingsKeyWithoutYear; + + let filteredTerms = availableTermOptions.filter(term => { + return termMonthsOptions.indexOf(term.value) > -1; + }); + if (filteredTerms.length) { + filteredTerms = TermOptions.insertTextDecorations(filteredTerms); + } + filteredTerms.unshift(TermOptions.defaultTermOption); + return filteredTerms; + } + + public fNum(num: number): number { + return parseInt(num.toString().replace(/[^0-9]/g, ''), 10); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/term-input-fields.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/term-input-fields.ts new file mode 100644 index 0000000..63c2103 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/term-input-fields.ts @@ -0,0 +1,18 @@ +import { LoanCode } from '../loan-type/loan-code'; +import { VehicleType } from '../vehicle-type/vehicle-type'; + +export class TermInputFields { + type: LoanCode; + year: number; + amount: number; + miles: number; + vehicleType?: VehicleType; + + constructor(type: LoanCode, year: number, amount: number, miles: number, vehicleType?: VehicleType) { + this.type = type; + this.year = year; + this.amount = amount; + this.miles = miles; + this.vehicleType = vehicleType; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/vehicle-term-calculator.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/vehicle-term-calculator.spec.ts new file mode 100644 index 0000000..86f4a85 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/vehicle-term-calculator.spec.ts @@ -0,0 +1,157 @@ +import { VehicleTermCalculator } from './vehicle-term-calculator'; +import { TermInputFields } from './term-input-fields'; +import { LoanCode } from '../loan-type/loan-code'; +import { TermOption } from '../configuration/ui-settings'; +import { DefaultSettings } from '../configuration/default-settings.mock'; +import { VehicleType } from '../vehicle-type/vehicle-type'; + +const mockTermOptions = { + 2012: [36, 48], + 2013: [36, 48, 63], + 2014: [36, 48, 63, 75], + 2018: [36, 48, 63, 75], + 2019: [36, 48, 63, 75, 84], + 2022: [36, 48, 63, 75], + 2023: [36, 48, 63, 75, 84] +}; + +const vehicle1899: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 1899, 5000, 10000); +const vehicle1900: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 1900, 5000, 10000); +const vehicle1950: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 1950, 5000, 10000); +const vehicle2012: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 2012, 5000, 10000); +const vehicle2023LowAmount: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 2023, 5000, 10000); +const vehicle2023HighAmount: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 2023, 30000, 1000); +const vehicle2022HighAmount: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 2022, 30000, 1000); +const vehicle2030HighAmount: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 2030, 30000, 1000); +const vehicle2030CycleLowAmount: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 2030, 14000, 1000, VehicleType.CYCLE); +const vehicle2030CycleHighAmount: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 2030, 16000, 1000, VehicleType.CYCLE); +const vehicle2050: TermInputFields = new TermInputFields(LoanCode.VEHICLE_DEFAULT, 2050, 30000, 1000); + +describe('VehicleTermCalculator', () => { + const mockAvailableTermOptions = >DefaultSettings.termOptionsNoYear; + + it('should determine year options', () => { + const calc = new VehicleTermCalculator(vehicle2023HighAmount, mockTermOptions, mockAvailableTermOptions); + spyOn(calc, 'determineYearOptions').and.callThrough(); + let options = calc.determineYearOptions(mockTermOptions, vehicle2012); + expect(options).toEqual([36, 48]); + options = calc.determineYearOptions(mockTermOptions, vehicle2023HighAmount); + expect(options).toEqual([36, 48, 63, 75, 84]); + options = calc.determineYearOptions(mockTermOptions, vehicle2023HighAmount); + expect(options).toEqual([36, 48, 63, 75, 84]); + options = calc.determineYearOptions(mockTermOptions, vehicle1900); + expect(options).toEqual([36, 48]); + options = calc.determineYearOptions(mockTermOptions, vehicle1899); + expect(options).toEqual([]); + options = calc.determineYearOptions(mockTermOptions, vehicle2050); + expect(options).toEqual([]); + expect(calc.determineYearOptions).toHaveBeenCalled(); + }); + + it('should build a standard list of vehicle terms. 2030 year/high amount', () => { + const calc = new VehicleTermCalculator(vehicle2030HighAmount, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'}, + {value: 75, desc: '75 months'}, + {value: 84, desc: '84 months'}, + ]); + }); + + it('should build a standard list of vehicle terms. 2023 year/high amount', () => { + const calc = new VehicleTermCalculator(vehicle2023HighAmount, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'}, + {value: 75, desc: '75 months'}, + {value: 84, desc: '84 months'}, + ]); + }); + + it('should build a filtered list of vehicle terms for motorcycle below 15000', () => { + const calc = new VehicleTermCalculator(vehicle2030CycleLowAmount, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'}, + {value: 75, desc: '75 months'}, + ]); + }); + + it('should build a standard list of vehicle terms for motorcycle at 15000 or above', () => { + const calc = new VehicleTermCalculator(vehicle2030CycleHighAmount, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'}, + {value: 75, desc: '75 months'}, + {value: 84, desc: '84 months'}, + ]); + }); + + it('should build a filtered list of vehicle terms. 2023 year/low amount', () => { + const calc = new VehicleTermCalculator(vehicle2023LowAmount, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'}, + {value: 75, desc: '75 months'}, + ]); + }); + + it('should build a standard list of vehicle terms. 2022 year/high amount', () => { + const calc = new VehicleTermCalculator(vehicle2022HighAmount, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + {value: 63, desc: '63 months'}, + {value: 75, desc: '75 months'} + ]); + }); + + it('should build a standard list of vehicle terms based on 2012 year', () => { + const calc = new VehicleTermCalculator(vehicle2012, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + ]); + }); + + it('should build a default list of vehicle terms based on 1950 year', () => { + const calc = new VehicleTermCalculator(vehicle1950, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([ + {value: '', desc: 'FORMS.GENERAL.SELECTDEFAULT'}, + {value: 36, desc: 'Up to 36 months'}, + {value: 48, desc: '48 months'}, + ]); + }); + + it('should build a empty list of vehicle terms based on 1899 year', () => { + const calc = new VehicleTermCalculator(vehicle1899, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([]); + }); + + it('should build a empty list of vehicle terms based on 2050 year', () => { + const calc = new VehicleTermCalculator(vehicle2050, mockTermOptions, mockAvailableTermOptions); + expect(calc.determineTerm()) + .toEqual([]); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/vehicle-term-calculator.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/vehicle-term-calculator.ts new file mode 100644 index 0000000..38b6bcd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/terms/vehicle-term-calculator.ts @@ -0,0 +1,66 @@ +import { AvailableTermsCalculator } from './available-terms-calculator'; +import { TermInputFields } from './term-input-fields'; +import { TermOptions } from 'src/app/shared/options/term-options'; +import { CreditApplicationValues } from '../credit-application-values'; +import { TermOption } from '../configuration/ui-settings'; +import { VehicleType } from '../vehicle-type/vehicle-type'; + +export class VehicleTermCalculator implements AvailableTermsCalculator { + private fields: TermInputFields; + private termMonthsOptions: any; + private availableTermOptions: Array; + + constructor(fields: TermInputFields, termMonthsOptions: any, availableTermOptions: Array) { + this.fields = fields; + this.termMonthsOptions = termMonthsOptions; + this.availableTermOptions = availableTermOptions; + } + + public determineTerm(): Array<{ value: any, desc: string }> { + return this.buildTerms(this.termMonthsOptions, this.fields, this.availableTermOptions); + } + + public buildTerms( + termMonthsOptions: any, + fields: TermInputFields, + availableTermOptions: Array): Array<{ value: any, desc: string }> { + const yearOptions = this.determineYearOptions(termMonthsOptions, fields); + const minimumAmount = fields.vehicleType === VehicleType.CYCLE ? 15000 : 25000; + if (yearOptions && yearOptions.length > 0) { + let filteredTerms = availableTermOptions.filter(term => { + return yearOptions.indexOf(term.value) > -1; + }); + if (filteredTerms.length) { + filteredTerms = TermOptions.insertTextDecorations(filteredTerms); + } + filteredTerms.unshift(TermOptions.defaultTermOption); + if (this.fNum(fields.year) >= CreditApplicationValues.VEHICLE_CURRENT_YEAR) { + if (this.fNum(fields.amount) < minimumAmount) { + filteredTerms.pop(); + } + } + return filteredTerms; + } + return []; + } + + public determineYearOptions(options: any, fields: TermInputFields): Array { + if (this.fNum(fields.year) >= CreditApplicationValues.VEHICLE_CUT_OFF_YEAR_LOWER && + this.fNum(fields.year) <= CreditApplicationValues.VEHICLE_CUT_OFF_YEAR_UPPER) { + let yearOptions = options[this.fNum(fields.year)]; + if (!yearOptions) { + if (this.fNum(fields.year) > CreditApplicationValues.VEHICLE_CURRENT_YEAR) { + yearOptions = options[CreditApplicationValues.VEHICLE_CURRENT_YEAR]; + } else { + yearOptions = options[Object.keys(options)[0]]; + } + } + return yearOptions; + } + return []; + } + + public fNum(num: number): number { + return parseInt(num.toString().replace(/[^0-9]/g, ''), 10); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.html new file mode 100644 index 0000000..0830340 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.html @@ -0,0 +1,8 @@ +

TIMEOUT.TITLE

+

TIMEOUT.REASON

+

{{infoMessage}}

+
+
+ +
+
diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.spec.ts new file mode 100644 index 0000000..f5df5af --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.spec.ts @@ -0,0 +1,52 @@ +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TimeoutLandingPageComponent } from './timeout-landing-page.component'; +import { CreditApplicationDaoService } from '../credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../testing/shared-mocks/credit-application-dao.mock.service'; +import { configureTestSuite } from 'ng-bullet'; + + +describe('TimeoutPageComponent', () => { + let component: TimeoutLandingPageComponent; + let fixture: ComponentFixture; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot(), + ReactiveFormsModule + ], + declarations: [ + TimeoutLandingPageComponent + ], + providers: [ + { provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService } + ], + schemas: [ NO_ERRORS_SCHEMA ] + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TimeoutLandingPageComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should bind the home button to the correct command function', async(() => { + spyOn(component, 'commandBackHome'); + const button = fixture.debugElement.query(By.css('#buttonGoHome')).nativeElement; + button.click(); + fixture.whenStable().then(() => { + expect(component.commandBackHome).toHaveBeenCalled(); + }); + })); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.ts new file mode 100644 index 0000000..8ac20f2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-landing-page.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit } from '@angular/core'; +import { Router, ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'lw-timeout-landing', + templateUrl: './timeout-landing-page.component.html' +}) +export class TimeoutLandingPageComponent implements OnInit { + public readonly adminRoute = '/admin'; + public readonly defaultRoute = '/'; + public infoMessage = 'TIMEOUT.INFO'; + private home: string; + + constructor(private router: Router, private route: ActivatedRoute) { + } + + ngOnInit() { + this.route.queryParams.subscribe(params => this.home = params['home'] || this.defaultRoute); + this.infoMessage = this.getInfoMessage(); + } + + public commandBackHome() { + this.router.navigate([this.home], {relativeTo: null}); + } + + public getInfoMessage() { + if (this.home === this.adminRoute) { + return 'TIMEOUT.INFO_ADMIN'; + } else { + return 'TIMEOUT.INFO'; + } + } + +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.html b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.html new file mode 100644 index 0000000..20cae27 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.html @@ -0,0 +1,18 @@ + + + + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.spec.ts new file mode 100644 index 0000000..da495fc --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.spec.ts @@ -0,0 +1,192 @@ +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { Subject } from 'rxjs'; +import { configureTestSuite } from 'ng-bullet'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { TimeoutPopupComponent } from './timeout-popup.component'; +import { TimeoutPopupService } from './timeout-popup.service'; + +class ModalRefStub { + close = jasmine.createSpy('close'); + result = Promise.resolve('Extend'); +} + +class NgbModalStub { + open = jasmine.createSpy('open').and.callFake(() => new ModalRefStub()); +} + +class TimeoutPopupServiceStub { + private timerSubject = new Subject(); + timeout$ = this.timerSubject.asObservable(); + + start = jasmine.createSpy('start'); + restart = jasmine.createSpy('restart'); + clear = jasmine.createSpy('clear'); + clearAllData = jasmine.createSpy('clearAllData'); + navigateToTimeoutPage = jasmine.createSpy('navigateToTimeoutPage'); + navigateToHomePage = jasmine.createSpy('navigateToHomePage'); + + emitTimer(value: number) { + this.timerSubject.next(value); + } +} + +describe('TimeoutPopupComponent', () => { + let component: TimeoutPopupComponent; + let fixture: ComponentFixture; + let timeoutService: TimeoutPopupServiceStub; + let modalService: NgbModalStub; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + declarations: [TimeoutPopupComponent], + providers: [ + { provide: TimeoutPopupService, useClass: TimeoutPopupServiceStub }, + { provide: NgbModal, useClass: NgbModalStub } + ], + schemas: [NO_ERRORS_SCHEMA] + }); + + TestBed.overrideComponent(TimeoutPopupComponent, { + set: { template: '' } + }); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TimeoutPopupComponent); + component = fixture.componentInstance; + timeoutService = TestBed.get(TimeoutPopupService) as TimeoutPopupServiceStub; + modalService = TestBed.get(NgbModal) as NgbModalStub; + fixture.detectChanges(); + }); + + it('should create and start the timer', () => { + expect(component).toBeTruthy(); + expect(timeoutService.start).toHaveBeenCalledWith(900, component.warningAt); + }); + + it('should open the warning modal when warning threshold is hit', () => { + spyOn(component, 'openWarning').and.callThrough(); + timeoutService.emitTimer(component.warningAt); + expect(component.openWarning).toHaveBeenCalled(); + expect(modalService.open).toHaveBeenCalled(); + }); + + it('should open warning when timer emits signal', () => { + const openWarningSpy = spyOn(component, 'openWarning').and.callThrough(); + const receivedValues: number[] = []; + const subscription = component.warningTimer$.subscribe(value => receivedValues.push(value)); + + timeoutService.emitTimer(component.warningAt); + + expect(receivedValues[0]).toBe(component.warningAt); + expect(openWarningSpy).toHaveBeenCalled(); + subscription.unsubscribe(); + }); + + it('should bind the extend button to the correct command function (leave session)', fakeAsync(() => { + const modalRef = new ModalRefStub(); + modalRef.result = Promise.resolve('Extend'); + component.localState.appTimedOut = true; + modalService.open.and.returnValue(modalRef); + + component.openWarning(); + tick(); + + expect(timeoutService.navigateToHomePage).not.toHaveBeenCalled(); + expect(timeoutService.restart).toHaveBeenCalled(); + expect(component.localState.appTimedOut).toBeFalsy(); + })); + + it('should navigate to timeout page after timeout', fakeAsync(() => { + timeoutService.navigateToTimeoutPage.calls.reset(); + timeoutService.clearAllData.calls.reset(); + component.openWarning(); + component.localState.appTimedOut = true; + + timeoutService.emitTimer(-1); + tick(); + + expect(timeoutService.navigateToTimeoutPage).toHaveBeenCalled(); + expect(timeoutService.clearAllData).toHaveBeenCalled(); + })); + + it('should not navigate to timeout page while timer is still going', () => { + timeoutService.navigateToTimeoutPage.calls.reset(); + timeoutService.clearAllData.calls.reset(); + timeoutService.emitTimer(component.warningAt + 10); + + expect(timeoutService.navigateToTimeoutPage).not.toHaveBeenCalled(); + expect(timeoutService.clearAllData).not.toHaveBeenCalled(); + }); + + it('should trigger timeout flow when timer value drops below zero', fakeAsync(() => { + component.openWarning(); + component.localState.appTimedOut = true; + timeoutService.emitTimer(-1); + tick(); + expect(timeoutService.clearAllData).toHaveBeenCalled(); + expect(timeoutService.navigateToTimeoutPage).toHaveBeenCalled(); + })); + + it('should reset timer on user action', () => { + component.userAction(); + expect(timeoutService.restart).toHaveBeenCalled(); + }); + + it('should reset timer when user is active ( uses keyboard / mouse )', () => { + timeoutService.restart.calls.reset(); + + component.userAction(); + + expect(timeoutService.restart).toHaveBeenCalledTimes(1); + }); + + it('should allow sessionLogic to call warning', () => { + const warningSpy = spyOn(component, 'sessionTimeoutWarning'); + + component.sessionLogic(component.warningAt); + + expect(warningSpy).toHaveBeenCalled(); + }); + + it('should allow sessionLogic to call timeout with loantype', () => { + const modalRef = new ModalRefStub(); + component['modalReference'] = modalRef as any; + component.localState.appTimedOut = true; + const timeoutSpy = spyOn(component, 'sessionTimeout').and.callThrough(); + + component.sessionLogic(-1); + + expect(timeoutSpy).toHaveBeenCalled(); + expect(modalRef.close).toHaveBeenCalledWith('TimedOut'); + expect(timeoutService.clearAllData).toHaveBeenCalled(); + expect(timeoutService.navigateToTimeoutPage).toHaveBeenCalled(); + expect(component.localState.appTimedOut).toBeFalsy(); + }); + + it('should close the modal when session timeout occurs', fakeAsync(() => { + component.openWarning(); + const modalRef = modalService.open.calls.mostRecent().returnValue as ModalRefStub; + component.localState.appTimedOut = true; + component.sessionTimeout(); + tick(); + expect(modalRef.close).toHaveBeenCalledWith('TimedOut'); + })); + + it('should call sessionTimeoutWarning only once per cycle', () => { + spyOn(component, 'sessionTimeoutWarning').and.callThrough(); + timeoutService.emitTimer(component.warningAt); + timeoutService.emitTimer(component.warningAt); + expect(component.sessionTimeoutWarning).toHaveBeenCalledTimes(2); + }); + + it('should subscribe to timeout service', () => { + const subscribeSpy = spyOn(timeoutService.timeout$, 'subscribe').and.callThrough(); + + component.subscribeToTimer(); + + expect(subscribeSpy).toHaveBeenCalled(); + }); +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.ts new file mode 100644 index 0000000..8c4ffaf --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.component.ts @@ -0,0 +1,99 @@ +import { Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { TimeoutPopupService } from './timeout-popup.service'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { Observable, Subscription } from 'rxjs'; + +@Component({ + selector: 'lw-timeout-popup', + templateUrl: './timeout-popup.component.html' +}) +export class TimeoutPopupComponent implements OnInit, OnDestroy { + + // times are in term of seconds + private readonly timeoutDuration = 900; + private modalReference: NgbModalRef; + private warningTimerSubReference: Subscription; + + public readonly warningAt = 30; + public warningTimer$: Observable; + @ViewChild('content') modalContent: ElementRef; + + public localState = { + appTimedOut: false + }; + + // check for key input, mouse click, or mouse scroll wheel for user activity + @HostListener('document:keyup', ['$event']) + @HostListener('document:click', ['$event']) + @HostListener('document:wheel', ['$event']) + userAction() { + this.timeoutService.restart(); + } + + constructor(private timeoutService: TimeoutPopupService, private modalService: NgbModal) { + } + + public ngOnInit() { + this.timeoutService.start(this.timeoutDuration, this.warningAt); + this.subscribeToTimer(); + } + + public ngOnDestroy() { + this.timeoutService.clear(); + if ( this.warningTimerSubReference ) { + this.warningTimerSubReference.unsubscribe(); + } + } + + public subscribeToTimer() { + this.warningTimer$ = this.timeoutService.timeout$; + this.warningTimerSubReference = this.warningTimer$.subscribe( + x => this.sessionLogic(x), + e => console.error(e)); + } + + public sessionLogic( timerValue: number) { + if ( timerValue === this.warningAt ) { + this.sessionTimeoutWarning(); + } + if ( timerValue < 0) { + this.sessionTimeout(); + this.localState.appTimedOut = false; + } + } + + public openWarning() { + this.modalReference = this.modalService.open(this.modalContent); + this.modalReference.result.then((result) => { + if (result === 'Home') { + this.timeoutService.navigateToHomePage(); + } + if ( result !== 'TimedOut') { + this.resetTimer(); + } + }, () => { + // user rejects the modal, via escape/clicking on backdrop + this.resetTimer(); + }); + } + + public resetTimer() { + this.localState.appTimedOut = false; + this.timeoutService.restart(); + } + + public sessionTimeout() { + this.modalReference.close('TimedOut'); + if (this.localState.appTimedOut) { + this.timeoutService.clearAllData(); + this.timeoutService.navigateToTimeoutPage(); + } + } + + public sessionTimeoutWarning() { + if (!this.localState.appTimedOut) { + this.localState.appTimedOut = true; + this.openWarning(); + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.service.spec.ts new file mode 100644 index 0000000..3db5b84 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.service.spec.ts @@ -0,0 +1,90 @@ +import { Router } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TimeoutPopupService } from './timeout-popup.service'; +import { ApplicationWizardService } from '../../loan-wizard/application-wizard.service'; +import { CreditApplicationDaoService } from '../credit-application-dao.service'; +import { MockCreditApplicationDaoService } from '../../testing/shared-mocks/credit-application-dao.mock.service'; +import { TimeoutLandingPageComponent } from './timeout-landing-page.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { configureTestSuite } from 'ng-bullet'; + + +describe('Timeout Popup Service', () => { + + let service: TimeoutPopupService; + let applicationWizardService: ApplicationWizardService; + let router: Router; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([ + { path: 'timeout', component: TimeoutLandingPageComponent} + ]), + NgbModule.forRoot(), + ReactiveFormsModule, + TranslateModule.forRoot() + ], + declarations: [TimeoutLandingPageComponent], + providers: [ + {provide: CreditApplicationDaoService, useValue: MockCreditApplicationDaoService} + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(TimeoutPopupService); + applicationWizardService = TestBed.get(ApplicationWizardService); + router = TestBed.get(Router); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + it('should start a timer', () => { + spyOn(service, 'start'); + service.start(); + expect(service.start).toHaveBeenCalled(); + }); + + it('should reset a timer', () => { + spyOn(service, 'restart'); + service.restart(); + expect(service.restart).toHaveBeenCalled(); + }); + + it('should clear a timer', () => { + spyOn(service, 'clear').and.callThrough(); + spyOn(service, 'start').and.callThrough(); + service.start(); + service.clear(); + expect(service.clear).toHaveBeenCalled(); + }); + + it('should clear all form data', () => { + spyOn(service, 'clearAllData').and.callThrough(); + spyOn( applicationWizardService, 'handlePostApplicationEvents'); + service.clearAllData(); + expect(service.clearAllData).toHaveBeenCalled(); + expect(applicationWizardService.handlePostApplicationEvents).toHaveBeenCalled(); + }); + + it('should navigate to timeout page', () => { + spyOn(service, 'navigateToTimeoutPage').and.callThrough(); + spyOn(router, 'navigate').and.callThrough(); + service.navigateToTimeoutPage(); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should navigate to home page', () => { + spyOn(service, 'navigateToHomePage').and.callThrough(); + spyOn(router, 'navigate'); + service.navigateToHomePage(); + expect(router.navigate).toHaveBeenCalled(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.service.ts new file mode 100644 index 0000000..4a1103e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/timeout/timeout-popup.service.ts @@ -0,0 +1,103 @@ +import { Injectable } from '@angular/core'; +import { ApplicationWizardService } from '../../loan-wizard/application-wizard.service'; +import { Router } from '@angular/router'; +import { Observable, Subject, Subscription, timer } from 'rxjs'; +import { environment } from 'src/environments/environment'; +import { CreditApplication } from '../credit-application'; + +@Injectable({ + providedIn: 'root' +}) +export class TimeoutPopupService { + + private running = false; + private currentCount = 0; + private warnAt = 0; + private tickerInterval: any; + private tickerIntervalSubscription: Subscription; + private initialCountValue = 0; + private adminRoute = '/admin'; + + private timeoutSubject: Subject = new Subject(); + public timeout$: Observable; + + constructor(private applicationWizard: ApplicationWizardService, private router: Router) { + } + + public start(countdownFrom: number = 100, warnAt: number = 30) { + this.running = true; + this.currentCount = countdownFrom; + this.initialCountValue = countdownFrom; + this.warnAt = warnAt; + this.tickerInterval = timer(1000, 1000); + if (environment.enableTimeout) { + this.tickerIntervalSubscription = this.tickerInterval.subscribe(() => this.tick()); + } + this.timeout$ = this.timeoutSubject.asObservable(); + } + + public clear() { + this.running = false; + this.currentCount = this.initialCountValue; + if (this.timeoutSubject) { + this.timeoutSubject.unsubscribe(); + } + if (this.tickerIntervalSubscription) { + this.tickerIntervalSubscription.unsubscribe(); + } + } + + public restart() { + this.currentCount = this.initialCountValue; + if (!this.running) { + this.running = true; + } + } + + public tick() { + if (this.currentCount <= this.warnAt && this.running) { + this.timeoutSubject.next(this.currentCount); + } + if (this.currentCount === 0) { + this.timeoutSubject.next(-1); + this.running = false; + } + this.currentCount--; + } + + public clearAllData() { + this.applicationWizard.handlePostApplicationEvents(); + } + + public navigateToTimeoutPage() { + const creditApplication: CreditApplication = this.applicationWizard.getFullCreditApplication(); + if (creditApplication && creditApplication.hasBeenCompleted) { + location.href = 'https://www.landmarkcu.com'; + } + this.router.navigate(['/timeout'], { + queryParams: { + home: this.selectHomePage(), + }, + relativeTo: null + }).catch( + reason => (console.error('Cannot navigate to timeout: ' + reason))); + } + + public navigateToHomePage() { + this.router.navigate([this.selectHomePage()], {relativeTo: null}); + } + + public selectHomePage() { + if (this.router.url.includes(this.adminRoute)) { + return this.adminRoute; + } else { + return '/'; + } + } + +} + + + + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/motor-type.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/motor-type.ts new file mode 100644 index 0000000..5236968 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/motor-type.ts @@ -0,0 +1,6 @@ +export enum MotorType { + INBOARD = 1, + OUTBOARD = 2, + INBOARD_OUTBOARD = 3 + } + \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/trailer-type.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/trailer-type.ts new file mode 100644 index 0000000..47aed6a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/trailer-type.ts @@ -0,0 +1,7 @@ +export enum TrailerType { + TANDEM_AXEL = 'TandemAxel', + SINGLE_AXEL = 'SingleAxel', + TRI_AXLE = 'TriAxle' +} + +// 'Axle' vs 'Axel' spelling here is correct according to ACTion mapping \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/vehicle-type.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/vehicle-type.ts new file mode 100644 index 0000000..1f29e22 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/vehicle-type/vehicle-type.ts @@ -0,0 +1,12 @@ + + +export enum VehicleType { + AUTO = 'Automobile', + CYCLE = 'Motorcycle', + BOAT = 'Boat', + TRAILER= 'TrailerMotorHome', + ATV = 'ATV', + SNOW = 'Snowmobile', + PWC = 'PWC', + RV = 'RecreationalVehicle' +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/window.service.spec.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/window.service.spec.ts new file mode 100644 index 0000000..cb4b7e4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/window.service.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { WindowService } from './window.service'; +import { configureTestSuite } from 'ng-bullet'; + +describe('WindowService', () => { + + let service: WindowService; + + configureTestSuite(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule, + NgbModule.forRoot() + ], + schemas: [ + NO_ERRORS_SCHEMA + ], + declarations: [ + + ] + }); + }); + + beforeEach(() => { + service = TestBed.get(WindowService); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + +}); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/shared/window.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/window.service.ts new file mode 100644 index 0000000..82152b7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/shared/window.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class WindowService { + + public showWindowForId(windowTargetId: string): void { + let contentHtml, popupWin; + contentHtml = document.getElementById(windowTargetId).innerHTML; + popupWin = window.open('', '_blank', 'top=0,left=0,height=auto,width=auto'); + popupWin.document.open(); + popupWin.document.write(` + + + Terms + + ${contentHtml} + ` + ); + popupWin.document.close(); + } + + public showPrintWindowForId(windowTargetId: string): void { + let printContents, popupWin; + printContents = document.getElementById(windowTargetId).innerHTML; + popupWin = window.open('', '_blank', 'top=0,left=0,height=100%,width=auto'); + popupWin.document.open(); + popupWin.document.write(` + + + Print Terms + + + ${printContents} + ` + ); + popupWin.document.close(); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/activated-route.mock.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/activated-route.mock.ts new file mode 100644 index 0000000..7eb4366 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/activated-route.mock.ts @@ -0,0 +1,8 @@ +import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; + +export class MockActivatedRoute extends ActivatedRoute { + snapshot = new ActivatedRouteSnapshot(); + constructor(options) { + super(); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/api-translator-pre-post.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/api-translator-pre-post.ts new file mode 100644 index 0000000..73f744a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/api-translator-pre-post.ts @@ -0,0 +1,348 @@ +import { LoanTypeSet } from '../../shared/loan-type'; +import { CreditApplicationValues } from '../../shared/credit-application-values'; +import { CreditApplication } from '../../shared/credit-application'; +import { LoanPurpose } from 'src/app/shared/loan-type/loan-purpose'; +import { ApiStatus } from 'src/app/shared/api-models/api-status'; +import { ApiErrorCode } from 'src/app/shared/api-models/api-error'; +import { ApiLoanApplication } from 'src/app/shared/api-models/api-loan-application'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { MfgHomeTypeEnum, OccupancyTypeEnum, PhoneTypeEnum } from 'src/app/shared/api-models/api-enums'; +import {LoanTitle} from '../../shared/loan-type/loan-title'; + +export const apiTranslatorPreTranslate: CreditApplication = { + 'loanType': LoanTypeSet[8], + 'note': 'This is a note!', + 'primaryContactMethod': '262-623-8990', + 'hasBeenCompleted': false, + 'disclosureInformation': { + 'truthful': true, + 'contact': true, + 'date': new Date() + }, + 'coApplicantLivesInMaritalPropertyState': true, + 'fullInfoBorrowers': [ + { + 'addressInformation': { + 'addressLine1': '123 Main St', + 'addressLine2': '', + 'zipcode': '53202', + 'city': 'Milwaukee', + 'state': CreditApplicationValues.STATE_REQUIRED_COAPP + }, + 'residenceInformation': { + 'isMailingAddressSame': true, + 'residenceMonthlyPayment': 500, + 'residenceTimeYears': '8', + 'residenceTimeMonths': '5', + 'residenceStatus': '0' + }, + 'personalInformation': { + 'firstName': 'Primary', + 'middleInitial': 'J', + 'lastName': 'Borrower', + 'dateOfBirth': '01/01/2010', + 'socialSecurityNumber': '666-55-5444', + 'primaryPhoneNumberType': PhoneTypeEnum.Home, + 'primaryPhoneNumber': '(555) 555-0134', + 'secondaryPhoneNumberType': null, + 'secondaryPhoneNumber': null, + 'email': 'primary@example.com', + 'maritalStatus': CreditApplicationValues.CHOICE_YES + }, + 'genderInformation': { + 'female': null, + 'male': true, + 'notProvided': null, + }, + 'ethnicityInformation': { + 'hispanicOrLatino': true, + 'mexican': null, + 'puertoRican': null, + 'cuban': null, + 'otherHispanicLatino': null, + 'otherHispanicLatinoText': null, + 'notHispanicOrLatino': null, + 'notProvided': null + }, + 'raceInformation': { + 'americanIndianAlaskaNative': null, + 'americanIndianAlaskaNativeText': null, + 'blackOrAfricanAmerican': null, + 'asian': null, + 'asianIndian': null, + 'chinese': null, + 'filipino': null, + 'japanese': null, + 'korean': null, + 'vietnamese': null, + 'otherAsian': true, + 'otherAsianText': 'Thai', + 'hawaiian': null, + 'nativeHawaiian': null, + 'guamanianChamorro': null, + 'samoan': null, + 'otherPacificIslander': null, + 'otherPacificIslanderText': null, + 'white': null, + 'notProvided': null + }, + 'employmentInformation': [{ + 'employerName': 'Petes Purple Pies', + 'employmentType': 'FullTime', + 'jobTitle': 'Lead Pie Prepper', + 'salary': 100000, + 'schedule': 'Annually', + 'employmentLengthYears': 10, + 'employmentLengthMonths': 0, + 'weeklyHours': null, + 'workPhoneNumber': '' + }] + }, { + 'addressInformation': { + 'addressLine1': '123 Main St', + 'addressLine2': '', + 'zipcode': '53202', + 'city': 'Milwaukee', + 'state': CreditApplicationValues.STATE_REQUIRED_COAPP + }, + 'residenceInformation': { + 'isMailingAddressSame': true, + 'residenceMonthlyPayment': 500, + 'residenceTimeYears': '8', + 'residenceTimeMonths': '5', + 'residenceStatus': '0' + }, + 'personalInformation': { + 'firstName': 'Spouse', + 'middleInitial': 'R', + 'lastName': 'Borrower', + 'dateOfBirth': '01/01/2009', + 'socialSecurityNumber': '666-33-3222', + 'primaryPhoneNumberType': PhoneTypeEnum.Home, + 'primaryPhoneNumber': '(555) 555-0199', + 'secondaryPhoneNumberType': null, + 'secondaryPhoneNumber': null, + 'email': 'coapp@example.org', + 'maritalStatus': CreditApplicationValues.CHOICE_YES + }, + 'genderInformation': { + 'female': null, + 'male': null, + 'notProvided': true + }, + 'ethnicityInformation': { + 'hispanicOrLatino': null, + 'mexican': null, + 'puertoRican': null, + 'cuban': null, + 'otherHispanicLatino': null, + 'otherHispanicLatinoText': null, + 'notHispanicOrLatino': null, + 'notProvided': null + }, + 'raceInformation': { + 'americanIndianAlaskaNative': null, + 'americanIndianAlaskaNativeText': null, + 'blackOrAfricanAmerican': null, + 'asian': null, + 'asianIndian': null, + 'chinese': null, + 'filipino': null, + 'japanese': null, + 'korean': null, + 'vietnamese': null, + 'otherAsian': null, + 'otherAsianText': null, + 'hawaiian': null, + 'nativeHawaiian': null, + 'guamanianChamorro': null, + 'samoan': null, + 'otherPacificIslander': null, + 'otherPacificIslanderText': null, + 'white': null, + 'notProvided': null + }, + 'otherIncomeInformation': [{ + 'amount': 100000, + 'incomeName': 'Bingo', + 'frequency': '' + }, + { + 'amount': 2000, + 'incomeName': 'Federal Check', + 'frequency': '' + }, + { + 'amount': 1000, + 'incomeName': 'Financial Aid', + 'frequency': '' + }] + } + ], + 'purposeOfLoan': LoanPurpose.Home_Purchase, + 'purposeOfLoanSignature': 'Other', + 'termOfLoan': '120', + 'hasCoApplicant': CreditApplicationValues.CHOICE_YES, + 'limitRequested': 25000, + 'confirmJointCredit': CreditApplicationValues.CHOICE_YES, + 'hasCoApplicantSpouse': CreditApplicationValues.CHOICE_YES, + 'collateralInformation': { + 'isAddressSame': CreditApplicationValues.CHOICE_NO, + 'estimatedValue': 25000, + 'propertyType': 'Quad', + 'hasFirstMortgage' : CreditApplicationValues.CHOICE_NO, + 'hasSecondMortgage' : CreditApplicationValues.CHOICE_NO, + 'payoffFirstMortgage' : false, + 'payoffSecondMortgage' : false, + 'isBeingImproved': CreditApplicationValues.CHOICE_NO, + 'isListedForSale': CreditApplicationValues.CHOICE_NO, + 'occupancyType': 'SecondHome', + 'onRentedLot': CreditApplicationValues.CHOICE_YES, + 'rentPaid': '250', + 'addressLine1': '987 N 1st St', + 'addressLine2': '', + 'zipcode': '53151', + 'city': 'New Berlin', + 'state': CreditApplicationValues.STATE_REQUIRED_COAPP + }, + 'status': ApiStatus.IN_PROGRESS, + 'page': { + 'hasAddress': true, + 'hasEmployment': true, + 'hasPersonal': true, + 'hasMemberInfo': true, + 'hasSpouseInfo': true, + 'hasTattleSpouseInfo': true, + 'hasResidenceInfo': true, + 'hasVehicleCollateral': true, + 'hasHMDA': true, + 'hasDisclosure': true, + 'hasCollateralInfo': true, + 'hasNote': true + }, + 'error': { + 'code': ApiErrorCode.No_Error, + 'message': 'No Error' + } +}; + +export const apiTranslatorPostTranslate: ApiLoanApplication = { + 'id': 'some-hashed-cosmos-id', + 'date': 'some-hashed-timestamp', + 'purpose': LoanPurpose.Home_Refinance, + 'purposeOfLoanSignature' : 'Other', + 'loanType': LoanCode.MANUFACTURED_HOME_LOAN, + 'loanTitle': LoanTitle.ManufacturedHome, + 'primaryContactMethod': '262-623-8990', + 'requestedAmount': 25000, + 'termLengthInMonths': 120, + 'manufacturedHome': { + 'type': MfgHomeTypeEnum.Quad, + 'use': OccupancyTypeEnum.SecondHome, + 'currentlyListedForSale': false, + 'currentlyBeingRemodeledOrImproved': false, + 'estimatedHomeValuation': 25000, + 'lot': { + 'address1': '123 Main St', + 'address2': '', + 'city': 'Milwaukee', + 'state': CreditApplicationValues.STATE_REQUIRED_COAPP, + 'postalCode': '53202', + 'postalCodeExt': null, + 'country': 'UnitedStates', + 'isRented': true, + 'rentalAmt': 250 + } + }, + 'primaryBorrower': { + 'firstName': 'Primary', + 'middleInitial': 'J', + 'lastName': 'Borrower', + 'dob': '2010-01-01T00:00:00', + 'ssn': '666555444', + 'phone': '5555550134', + 'phoneType': 'Home', + 'email': 'primary@example.com', + 'isMarried': true, + 'address': { + 'address1': '123 Main St', + 'address2': '', + 'city': 'Milwaukee', + 'state': CreditApplicationValues.STATE_REQUIRED_COAPP, + 'postalCode': '53202', + 'postalCodeExt': null, + 'country': 'UnitedStates', + 'residenceType': 'Own', + 'monthsResided': 101, + 'monthlyPayment': 500 + }, + 'previousAddresses': [], + 'hmdaValues': { + 'sexes': ['notProvided'], + 'races': ['OtherAsian'], + 'ethnicities': ['HispanicOrLatino'], + 'otherAsianRace': 'Thai' + }, + 'employers': [{ + 'name': 'Petes Purple Pies', + 'currentEmployer': true, + 'employmentType': 'FullTime', + 'title': 'Lead Pie Prepper', + 'income': { + 'sourceName': 'Petes Purple Pies', + 'frequency': 'Annually', + 'amountPerPeriod': 100000, + 'incomeTypeCD': 'Salary', + 'incomeStatus': 'IncomeStatus_Included' + }, + 'monthsEmployed': 120, + 'language': 'English', + 'workPhone': null + } + ], + 'language': 'English', + 'otherIncomes': [], + 'hasOptedOutTCPA': false + }, + 'coApplicant': { + 'firstName': 'Spouse', + 'middleInitial': 'R', + 'lastName': 'Borrower', + 'dob': '01/01/2009', + 'ssn': '666333222', + 'phone': '5555550199', + 'phoneType': 'Home', + 'email': 'coapp@example.org', + 'isMarried': true, + 'address': { + 'address1': '123 Main St', + 'address2': '', + 'city': 'Milwaukee', + 'state': CreditApplicationValues.STATE_REQUIRED_COAPP, + 'postalCode': '53202', + 'postalCodeExt': null, + 'country': 'UnitedStates', + 'residenceType': 'Own', + 'monthsResided': 101, + 'monthlyPayment': 0 + }, + 'previousAddresses': [], + 'hmdaValues': { + 'sexes': ['notProvided'], + 'races': ['notProvided'], + 'ethnicities': ['notProvided'] + }, + 'employers': [], + 'otherIncomes': [{ + 'incomeTypeCD': 'Other', + 'sourceName': 'Bingo', + 'frequency': 'Annually', + 'amountPerPeriod': 100000, + 'incomeStatus': 'IncomeStatus_Included' + } + ], + 'language': 'English', + 'isMarriedToPrimaryBorrower': true, + 'hasOptedOutTCPA': false + } +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/broken-applications.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/broken-applications.ts new file mode 100644 index 0000000..ee797a0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/broken-applications.ts @@ -0,0 +1,216 @@ +/** + * The first credit application here is when there is a co-applicant spouse and the spouse answered yes as having same address. + * The second credit application here siw hen the application goes back and changes to not applying with spouse. The has same address + * flag remains true, but coApplicantSpouse = "no" + */ +import { LoanType, LoanTypeSet } from '../../shared/loan-type'; + +export const primaryAndCoApplicationAddressSameApplication = { + 'loanType': LoanTypeSet[0], + 'hasBeenCompleted': false, + 'disclosureInformation': {'contact': true, 'truthful': false}, + 'fullInfoBorrowers': [{ + 'addressInformation': { + 'addressLine1': '526 E LINCOLN AVE', + 'addressLine2': null, + 'city': 'MILWAUKEE', + 'state': 'WI', + 'zipcode': '53207', + 'zipcodeExt': '1721', + 'residenceStatus': '0', + 'residenceTimeYears': '', + 'residenceTimeMonths': '0', + 'residenceMonthlyPayment': null, + 'isMailingAddressSame': true + }, + 'residenceInformation': { + 'residenceStatus': '0', + 'residenceTimeYears': '7', + 'residenceTimeMonths': '0', + 'residenceMonthlyPayment': '500', + 'isMailingAddressSame': true + }, + 'personalInformation': { + 'firstName': 'George', + 'middleInitial': 'J', + 'lastName': 'Frick', + 'suffix': null, + 'dateOfBirth': '12/02/2000', + 'socialSecurityNumber': '123-12-3123', + 'primaryPhoneNumberType': 0, + 'primaryPhoneNumber': '(414) 732-9928', + 'secondaryPhoneNumberType': null, + 'secondaryPhoneNumber': null, + 'email': 'george.frick@gmail.com', + 'maritalStatus': 'yes', + 'coApplicantSpouse': 'yes' + }, + 'spouseType': null, + 'fullInfoSpouse': null, + 'tattleSpouse': null, + 'priorResidenceInformation': [], + 'priorAddressInformation': [], + 'employmentInformation': [], + 'otherIncomeInformation': [] + }, { + 'addressInformation': {'addressLine1': '', 'addressLine2': '', 'city': '', 'state': 'WI', 'zipcode': ''}, + 'residenceInformation': { + 'isMailingAddressSame': true, + 'residenceMonthlyPayment': null, + 'residenceTimeYears': '', + 'residenceTimeMonths': '0', + 'residenceStatus': '0' + }, + 'personalInformation': { + 'firstName': 'Nicole', + 'middleInitial': 'J', + 'lastName': 'Frick', + 'suffix': null, + 'dateOfBirth': '12/02/2000', + 'socialSecurityNumber': '123-12-3123', + 'primaryPhoneNumberType': 0, + 'primaryPhoneNumber': '(919) 672-3828', + 'secondaryPhoneNumberType': null, + 'secondaryPhoneNumber': null, + 'email': 'george.frick@gmail.com', + 'maritalStatus': 'yes' + }, + 'hasSameAddressAsSpouse': 'yes', + 'employmentInformation': [], + 'otherIncomeInformation': [] + }], + 'status': 'In_Progress', + 'error': null, + 'page': { + 'hasAddress': true, + 'hasEmployment': true, + 'hasPersonal': true, + 'hasMemberInfo': true, + 'hasSpouseInfo': false, + 'hasTattleSpouseInfo': false, + 'hasResidenceInfo': true, + 'hasVehicleCollateral': false, + 'hasHMDA': false, + 'hasDisclosure': false, + 'hasCollateralInfo': false, + 'hasNote': false + }, + 'hasCoApplicant': 'yes', + 'limitRequested': 5000, + 'confirmJointCredit': 'yes', + 'coApplicantLivesInMaritalPropertyState': true, + 'hasCoApplicantSpouse': 'yes', + 'loanId': '964bc4-ff-4dea-c-69f9b0dbb214', + 'primaryContactMethod': 'email' +}; + +export const sameAddressFlagBroken2 = { + 'loanType': { + 'id': 'cb', + 'category': 'Credit Card', + 'title': 'Cash Back', + 'apiCode': 'CC_CashBack', + 'icon': 'credit-card', + 'imagePath': 'assets/cc-images/LCU_cashback.png', + 'message': 'COPY.TYPE_MESSAGE.CREDITCARD_CASHBACK', + 'headerMessageKey': 'HEADER.LOAN_MESSAGE.CREDITCARD_CASHBACK', + 'shouldDecision': true, + 'pages': [], + 'defaultPurpose': 'Other', + 'requiresCollateral': false + }, + 'hasBeenCompleted': false, + 'disclosureInformation': {'contact': true, 'truthful': false}, + 'fullInfoBorrowers': [{ + 'addressInformation': { + 'addressLine1': '526 E LINCOLN AVE', + 'addressLine2': null, + 'city': 'MILWAUKEE', + 'state': 'WI', + 'zipcode': '53207', + 'zipcodeExt': '1721', + 'residenceStatus': '0', + 'residenceTimeYears': '7', + 'residenceTimeMonths': '0', + 'residenceMonthlyPayment': '500', + 'isMailingAddressSame': true + }, + 'residenceInformation': { + 'residenceStatus': '0', + 'residenceTimeYears': '7', + 'residenceTimeMonths': '0', + 'residenceMonthlyPayment': '500', + 'isMailingAddressSame': true + }, + 'personalInformation': { + 'firstName': 'George', + 'middleInitial': 'J', + 'lastName': 'Frick', + 'suffix': '', + 'dateOfBirth': '12/02/2000', + 'socialSecurityNumber': '123-12-3123', + 'primaryPhoneNumberType': 0, + 'primaryPhoneNumber': '(414) 732-9928', + 'secondaryPhoneNumberType': null, + 'secondaryPhoneNumber': null, + 'email': 'george.frick@gmail.com', + 'maritalStatus': 'yes', + 'coApplicantSpouse': 'no' + }, + 'spouseType': null, + 'fullInfoSpouse': null, + 'tattleSpouse': null, + 'priorResidenceInformation': [], + 'priorAddressInformation': [], + 'employmentInformation': [], + 'otherIncomeInformation': [] + }, { + 'addressInformation': {'addressLine1': '', 'addressLine2': '', 'city': '', 'state': 'WI', 'zipcode': ''}, + 'residenceInformation': { + 'isMailingAddressSame': true, + 'residenceMonthlyPayment': null, + 'residenceTimeYears': '', + 'residenceTimeMonths': '0', + 'residenceStatus': '0' + }, + 'personalInformation': { + 'firstName': 'Nicole', + 'middleInitial': 'J', + 'lastName': 'Frick', + 'suffix': '', + 'dateOfBirth': '12/02/2000', + 'socialSecurityNumber': '123-12-3123', + 'primaryPhoneNumberType': 0, + 'primaryPhoneNumber': '(919) 672-3828', + 'secondaryPhoneNumberType': null, + 'secondaryPhoneNumber': null, + 'email': 'george.frick@gmail.com', + 'maritalStatus': 'yes' + }, + 'hasSameAddressAsSpouse': 'yes', + 'employmentInformation': [], + 'otherIncomeInformation': [] + }], + 'status': 'In_Progress', + 'error': null, + 'page': { + 'hasAddress': true, + 'hasEmployment': true, + 'hasPersonal': true, + 'hasMemberInfo': true, + 'hasSpouseInfo': false, + 'hasTattleSpouseInfo': false, + 'hasResidenceInfo': true, + 'hasVehicleCollateral': false, + 'hasHMDA': false, + 'hasDisclosure': false, + 'hasCollateralInfo': false, + 'hasNote': false + }, + 'hasCoApplicant': 'yes', + 'limitRequested': '5000', + 'confirmJointCredit': 'yes', + 'coApplicantLivesInMaritalPropertyState': true, + 'hasCoApplicantSpouse': 'no', + 'loanId': '964bca44-ffd8-4dea-8e5c-69f9b0dbb214' +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/configuration.mock.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/configuration.mock.service.ts new file mode 100644 index 0000000..c9349f0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/configuration.mock.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { environment } from '../../../environments/environment'; +import { UiSettings } from '../../shared/configuration/ui-settings'; + +@Injectable({ + providedIn: 'root' +}) +export class MockConfigurationService { + private uiSettings: UiSettings; + + // This is a mock so we don't use HttpClient. + constructor() { } + + public buildPingURL(): string { + return environment.urlBase + '/UiSettings/'; + } + + public reloadConfiguration(): Observable { + return of({}); + } + + public saveConfiguration(config: UiSettings): void { + this.uiSettings = config; + } + + public getConfigurationByField(field: string): any { + if (!this.uiSettings) { + throw new Error('UI Settings not loaded'); + } + return this.uiSettings[field]; + } + + public getAllConfiguration(): UiSettings { + if (this.uiSettings) { + return this.uiSettings; + } + throw new Error('UI Settings not loaded'); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/credit-application-dao.mock.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/credit-application-dao.mock.service.ts new file mode 100644 index 0000000..73f7c27 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/credit-application-dao.mock.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { CreditApplication } from '../../shared/credit-application'; +import { Observable, of } from 'rxjs'; +import { DecisionStatus } from '../../shared/decision/decision-status'; +import { ICreditApplicationDaoService } from '../../shared/credit-application-dao.service'; + +@Injectable({ + providedIn: 'root' +}) +export class MockCreditApplicationDaoService implements ICreditApplicationDaoService { + + // This is a mock so we don't use HttpClient. + constructor() { } + + public saveApplication(creditApplication: CreditApplication): Observable { + return of(null); + } + + createApplication(creditApplication: CreditApplication): Observable { + return of(null); + } + + decideApplication(creditApplication: CreditApplication): Observable { + return of(null); + } + + getApplicationDecision(creditApplication: CreditApplication): Observable { + return of(null); + } + + submitApplication(creditApplication: CreditApplication): Observable { + return of(null); + } + + updateApplication(creditApplication: CreditApplication): Observable { + return of(null); + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/loan-table.mock.service.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/loan-table.mock.service.ts new file mode 100644 index 0000000..7b14604 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/loan-table.mock.service.ts @@ -0,0 +1,81 @@ +import { Injectable } from '@angular/core'; +import { environment } from 'src/environments/environment'; +import { HttpHeaders } from '@angular/common/http'; +import { Observable, of, BehaviorSubject } from 'rxjs'; +import { ApiLoanApplication } from 'src/app/shared/api-models/api-loan-application'; +import { TestApiCreditApplication } from './test-api-credit-application'; + + + +export class MockLoanTableService { + public loanApp: ApiLoanApplication = TestApiCreditApplication; + public loanApps: ApiLoanApplication[] = [this.loanApp]; + + private applicationSet: BehaviorSubject> = new BehaviorSubject>(this.loanApps); + public applicationSet$ = this.applicationSet.asObservable(); + + constructor() {} + + public buildApiURL(): string { + return environment.urlBase + '/Storage'; + } + + public buildHeader(): HttpHeaders { + return new HttpHeaders().set('Authorization', 'Bearer ' + localStorage.getItem('adal.idtoken')); + } + + public hasApplications(): boolean { + return !!(this.applicationSet.value.length); + } + + public loadApplications(): Observable { + return of({}); + } + + public reloadApplications() { + this.loadApplications().subscribe( + (success: ApiLoanApplication[]) => { + this.setApplications(success); + } + ); + } + + public deleteApplication(loanId: string): Observable { + return of({}); + } + + public deleteAndLoad(loanId: string) { + this.deleteApplication(loanId).subscribe( + () => { + this.reloadApplications(); + } + ); + } + + public submitAndLoad(loanId: string) { + this.submitApplication(loanId).subscribe( + () => { + this.reloadApplications(); + } + ); + } + + public setApplications(applications: ApiLoanApplication[]): void { + this.applicationSet.next(applications); + } + + public submitApplication(loanId: string): Observable { + return of({}); + } + + public getApplicationById(loanId: string): ApiLoanApplication { + if (!this.applicationSet.value.length) { + throw new Error('Applications are not loaded'); + } + const single = this.applicationSet.value.find(x => x.id === loanId); + if (!single) { + throw new Error('Application not found'); + } + return single; + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-api-credit-application.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-api-credit-application.ts new file mode 100644 index 0000000..06e84c0 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-api-credit-application.ts @@ -0,0 +1,168 @@ +import { ApiLoanApplication } from '../../shared/api-models/api-loan-application'; +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import {LoanTitle} from '../../shared/loan-type/loan-title'; + +export const TestApiCreditApplication: ApiLoanApplication = { + 'id': '0', + 'loanType': LoanCode.CC_CashBack, + 'loanTitle': LoanTitle.CashBack, + 'note': 'This is a note!', + 'primaryContactMethod': '262-623-8990', + 'requestedAmount': 1000, + 'date': '2018-07-23', + 'primaryBorrower': { + 'hasOptedOutTCPA': false, + 'firstName': 'Dennis', + 'middleInitial': 'J', + 'lastName': 'Reynolds', + 'dob': '2000-07-23', + 'ssn': 'string', + 'phone': 'string', + 'phoneType': 'Mobile', + 'email': 'string', + 'address': { + 'address1': 'string', + 'address2': 'string', + 'city': 'string', + 'state': 'AA', + 'postalCode': 'string', + 'country': 'string', + 'monthsResided': 0, + 'residenceType': 'Own', + 'id': 'string' + }, + 'previousAddresses': [ + { + 'address1': 'string', + 'address2': 'string', + 'city': 'string', + 'state': 'AA', + 'postalCode': 'string', + 'country': 'string', + 'monthsResided': 0, + 'residenceType': 'Own', + 'id': 'string' + } + ], + 'employers': [ + { + 'id': 'string', + 'name': 'string', + 'currentEmployer': true, + 'employmentType': 'NoneSelected', + 'title': 'string', + 'income': { + 'incomeTypeCD': 'Alimony', + 'sourceName': 'string', + 'alimonyStatus': 'CourtOrdered', + 'amountPerPeriod': 0, + 'frequency': 'Annually', + 'hoursPerWeek': '0', + 'incomeAdjustmentPercentage': 0, + 'adjustedIncome': 0, + 'incomeStatus': 'IncomeStatus_Excluded' + }, + 'monthsEmployed': 0, + 'startDate': '2018-07-23T19:29:57.744Z', + 'endDate': '2018-07-23T19:29:57.744Z', + 'workPhone': 'string', + 'language': 'string' + } + ], + 'otherIncomes': [ + { + 'incomeTypeCD': 'Alimony', + 'sourceName': 'string', + 'alimonyStatus': 'CourtOrdered', + 'amountPerPeriod': 0, + 'frequency': 'Annually', + 'hoursPerWeek': '0', + 'incomeAdjustmentPercentage': 0, + 'adjustedIncome': 0, + 'incomeStatus': 'IncomeStatus_Excluded' + } + ], + 'isMarried': true, + 'language': 'string', + 'customerNumber': 'string', + 'id': 'string' + }, + 'coApplicant': + { + 'hasOptedOutTCPA': false, + 'firstName': 'string', + 'middleInitial': 'string', + 'lastName': 'string', + 'dob': '2018-07-23T19:29:57.744Z', + 'ssn': 'string', + 'phone': 'string', + 'phoneType': 'Mobile', + 'email': 'string', + 'address': { + 'address1': 'string', + 'address2': 'string', + 'city': 'string', + 'state': 'AA', + 'postalCode': 'string', + 'country': 'string', + 'monthsResided': 0, + 'residenceType': 'Own', + 'id': 'string' + }, + 'previousAddresses': [ + { + 'address1': 'string', + 'address2': 'string', + 'city': 'string', + 'state': 'AA', + 'postalCode': 'string', + 'country': 'string', + 'monthsResided': 0, + 'residenceType': 'Own', + 'id': 'string' + } + ], + 'employers': [ + { + 'id': 'string', + 'name': 'string', + 'currentEmployer': true, + 'employmentType': 'NoneSelected', + 'title': 'string', + 'income': { + 'incomeTypeCD': 'Alimony', + 'sourceName': 'string', + 'alimonyStatus': 'CourtOrdered', + 'amountPerPeriod': 0, + 'frequency': 'Annually', + 'hoursPerWeek': '0', + 'incomeAdjustmentPercentage': 0, + 'adjustedIncome': 0, + 'incomeStatus': 'IncomeStatus_Excluded' + }, + 'monthsEmployed': 0, + 'startDate': '2018-07-23T19:29:57.744Z', + 'endDate': '2018-07-23T19:29:57.744Z', + 'workPhone': 'string', + 'language': 'string' + } + ], + 'otherIncomes': [ + { + 'incomeTypeCD': 'Alimony', + 'sourceName': 'string', + 'alimonyStatus': 'CourtOrdered', + 'amountPerPeriod': 0, + 'frequency': 'Annually', + 'hoursPerWeek': '0', + 'incomeAdjustmentPercentage': 0, + 'adjustedIncome': 0, + 'incomeStatus': 'IncomeStatus_Excluded' + } + ], + 'isMarried': true, + 'language': 'string', + 'customerNumber': 'string', + 'id': 'string' + } +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-api-loan-container.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-api-loan-container.ts new file mode 100644 index 0000000..e675cf7 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-api-loan-container.ts @@ -0,0 +1,156 @@ +import { LoanCode } from 'src/app/shared/loan-type/loan-code'; +import { ApiLoanContainer } from 'src/app/shared/api-models/api-loan-container'; +import { ApiStatus } from 'src/app/shared/api-models/api-status'; +import { ApiError, ApiErrorCode } from 'src/app/shared/api-models/api-error'; +import { CreditApplication } from '../../shared/credit-application'; +import { LoanTypeSet } from '../../shared/loan-type'; + +export const TestTranslateCreditApplication: any = { + 'loanType': LoanTypeSet[13], + 'hasBeenCompleted': false, + 'fullInfoBorrowers': [{ + 'priorAddressInformation': [], + 'priorResidenceInformation': [], + 'addressInformation': { + 'addressLine1': '5445 S Westridge Dr', + 'addressLine2': '', + 'city': 'New Berlin', + 'state': 'WI', + 'zipcode': '53151' + }, + 'residenceInformation': { + 'residenceMonthlyPayment': 750, + 'residenceStatus': 0, + 'residenceTimeMonths': '0', + 'residenceTimeYears': '6', + 'isMailingAddressSame': true + }, + 'personalInformation': { + 'firstName': 'JOHN', + 'middleInitial': 'P', + 'lastName': 'DOE', + 'dateOfBirth': '01/01/1970', + 'socialSecurityNumber': '999-99-9990', + 'primaryPhoneNumberType': 1, + 'primaryPhoneNumber': '262-796-1100', + 'secondaryPhoneNumberType': 1, + 'secondaryPhoneNumber': '262-796-6370', + 'email': 'scottschmick@landmarkcu.com' + }, + 'ethnicityInformation': {'otherHispanicLatinoText': null}, + 'genderInformation': {}, + 'raceInformation': {'americanIndianAlaskaNativeText': null, 'otherAsianText': null, 'otherPacificIslanderText': null} + }], + 'hasCoApplicant': 'no', + 'hasCoApplicantSpouse': null, + 'primaryContactMethod': null, + // 'applicantType': 'no', + 'limitRequested': 5555, + // 'livesInMaritalPropertyState': 'yes', + 'coApplicantLivesInMaritalPropertyState': 'no', + 'purposeOfLoan': 'Other', + 'termOfLoan': 12, + 'page': null, + 'status': null, + 'error': null +}; + +export const TestApiLoanContainer: ApiLoanContainer = { + 'memberId': '1231', + 'individualId': '1231', + 'loanApplication': { + 'id': '0', + 'loanType': LoanCode.CC_CashBack, + 'note': 'This is a note!', + 'primaryContactMethod': '262-623-8990', + 'requestedAmount': 1000, + 'date': '2018-07-23', + 'primaryBorrower': { + 'hasOptedOutTCPA': false, + 'firstName': 'Dennis', + 'middleInitial': 'J', + 'lastName': 'Reynolds', + 'dob': '2018-07-23T19:29:57.744Z', + 'ssn': '123123123', + 'phone': '2622221215', + 'phoneType': 'Mobile', + 'email': 'f@f.com', + 'address': { + 'address1': '12123 Apple Lane', + 'address2': null, + 'city': 'Milwaukee', + 'state': 'WI', + 'postalCode': '53033', + 'country': 'UnitedStates', + 'monthlyPayment': 2222, + 'monthsResided': 84, + 'residenceType': 'Own', + 'id': '9e178efa' + }, + 'previousAddresses': [], + 'employers': null, + 'otherIncomes': null, + 'isMarried': false, + 'language': 'English', + 'customerNumber': '0', + 'id': 'a60d' + }, + 'coApplicant': + { + 'hasOptedOutTCPA': false, + 'firstName': 'John', + 'middleInitial': 'M', + 'lastName': 'Wick', + 'dob': '2018-07-23T19:29:57.744Z', + 'ssn': '355096548', + 'phone': '2316454567', + 'phoneType': 'Mobile', + 'email': 'r@r.com', + 'address': { + 'address1': '1243 Sugar Road', + 'address2': null, + 'city': 'Sussex', + 'state': 'WI', + 'postalCode': '53211', + 'country': 'UnitedStates', + 'monthlyPayment': 800, + 'monthsResided': 24, + 'residenceType': 'Own', + 'id': 'fja23s' + }, + 'previousAddresses': null, + 'employers': null, + 'otherIncomes': null, + 'isMarried': false, + 'language': 'English', + 'customerNumber': '0', + 'id': '12sd41' + } + }, + 'page': { + 'hasAddress': true, + 'hasEmployment': true, + 'hasPersonal': true, + 'hasMemberInfo': true, + 'hasSpouseInfo': true, + 'hasTattleSpouseInfo': true, + 'hasResidenceInfo': true, + 'hasVehicleCollateral': true, + 'hasHMDA': true, + 'hasDisclosure': true, + 'hasCollateralInfo': true, + 'hasNote': true + }, + 'status': ApiStatus.IN_PROGRESS, + 'error': { + code: ApiErrorCode.No_Error, + message: 'No Errors' + }, + 'date': new Date(), + 'id': '12341234', + 'loanId': '12341234', + 'loanType': LoanCode.CC_CashBack, + 'branch': 'milwaukee', + 'firstName': 'john', + 'lastName': 'sanders' +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-individual.mock.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-individual.mock.ts new file mode 100644 index 0000000..dde34cd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/test-individual.mock.ts @@ -0,0 +1,357 @@ +import { CoreIndividual } from '../../member/models/core-individual'; +import { PersonalInformation } from '../../loan-wizard/field-groups/personal/personal-information'; +import { PhoneTypeEnum } from '../../shared/api-models/api-enums'; +import { AddressInformation } from '../../loan-wizard/field-groups/address/address-information'; + +export const convertedPrimaryAddress = { + addressLine1: '9515 W National Ave', + addressLine2: '', + city: 'West Allis', + state: 'WI', + zipcode: '53227' +}; + +export const convertedPersonalInformation = { + firstName: 'TIMMY', + middleInitial: '', + lastName: 'TESTER', + dateOfBirth: '01/01/1990', + socialSecurityNumber: '395-76-1111', + primaryPhoneNumberType: PhoneTypeEnum.Mobile, + primaryPhoneNumber: '262-796-4501', + secondaryPhoneNumberType: PhoneTypeEnum.Mobile, + secondaryPhoneNumber: '262-796-4500', + email: 'lcu@landmarkcu.com' +}; + +export const testCoreIndividual = { + 'individualId': 731085, + 'individualType': 'Individual', + 'title': '', + 'firstName': 'TIMMY', + 'middleName': '', + 'lastName': 'TESTER', + 'nameSuffix': '', + 'taxId': '395-76-1111', + 'gender': '', + 'birthDate': '1990-01-01T00:00:00', + 'branch': 2, + 'driversLicense': '', + 'driversLicenseState': '', + 'ident': '', + 'preferredContact': 'No Preference', + 'maritalStatus': 'Unknown', + 'isUSCitizen': false, + 'primaryAddressId': 3644887, + 'isEmployee': false, + 'employeeNumber': 0, + 'companyName': '', + 'employerName': '', + 'applicationCountLast90Days': 0, + 'applicationsDeclinedLast90Days': 0, + 'applicationsApprovedLast90Days': 0, + 'address': [ + { + 'addressId': 1326362, + 'line1': '8035 W LORRAINE PL', + 'line2': '', + 'line3': '', + 'city': 'MILWAUKEE', + 'state': 'WI', + 'zipCode': '53222-4933', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 8035, + 'occupancy': 'None', + 'startDate': '0001-01-01T00:00:00', + 'endDate': '0001-01-01T00:00:00', + 'uspsVerified': true + }, { + 'addressId': 1531702, + 'line1': '5445 S WESTRIDGE DR', + 'line2': 'Att: Jeff Knaub', + 'line3': '', + 'city': 'NEW BERLIN', + 'state': 'WI', + 'zipCode': '53151-7948', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 5445, + 'occupancy': 'None', + 'startDate': '0001-01-01T00:00:00', + 'endDate': '0001-01-01T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 1596633, + 'line1': '106 E MAIN ST', + 'line2': '', + 'line3': '', + 'city': 'BEAVER DAM', + 'state': 'WI', + 'zipCode': '53916-1837', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 106, + 'occupancy': 'None', + 'startDate': '0001-01-01T00:00:00', + 'endDate': '0001-01-01T00:00:00', + 'uspsVerified': true + }, { + 'addressId': 2661829, + 'line1': '123 Test St', + 'line2': '', + 'line3': '', + 'city': 'New Berlin', + 'state': 'WI', + 'zipCode': '53151', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 123, + 'occupancy': 'None', + 'startDate': '0001-01-01T00:00:00', + 'endDate': '2016-01-07T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 2661878, + 'line1': '5445 S Westridge Dr', + 'line2': '', + 'line3': '', + 'city': 'New Berlin', + 'state': 'WI', + 'zipCode': '53151', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 5445, + 'occupancy': 'None', + 'startDate': '2016-01-07T00:00:00', + 'endDate': '2016-02-08T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 2675139, + 'line1': '110 E CLAYTON ST', + 'line2': '', + 'line3': '', + 'city': 'ATHENS', + 'state': 'GA', + 'zipCode': '30601', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 110, + 'occupancy': 'None', + 'startDate': '2016-02-08T00:00:00', + 'endDate': '2016-02-08T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 2675278, + 'line1': '110 TEST ST', + 'line2': '', + 'line3': '', + 'city': 'ATHENS', + 'state': 'GA', + 'zipCode': '30605', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 110, + 'occupancy': 'None', + 'startDate': '2016-02-08T00:00:00', + 'endDate': '2016-02-08T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 2675312, + 'line1': '5445 S WESTRIDGE DR', + 'line2': '', + 'line3': '', + 'city': 'NEW BERLIN', + 'state': 'WI', + 'zipCode': '53151', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 5445, + 'occupancy': 'None', + 'startDate': '2016-02-08T00:00:00', + 'endDate': '2016-02-09T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 2675535, + 'line1': 'P.O. BOX 510868', + 'line2': '', + 'line3': '', + 'city': 'NEW BERLIN', + 'state': 'WI', + 'zipCode': '53151', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 0, + 'occupancy': 'None', + 'startDate': '2016-02-09T00:00:00', + 'endDate': '2016-02-11T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 2676405, + 'line1': '5445 S WESTRIDGE DR', + 'line2': '', + 'line3': '', + 'city': 'NEW BERLIN', + 'state': 'WI', + 'zipCode': '53151', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 5445, + 'occupancy': 'None', + 'startDate': '2016-02-11T00:00:00', + 'endDate': '2016-02-11T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 2676427, + 'line1': 'P.O. BOX 510868', + 'line2': '', + 'line3': '', + 'city': 'NEW BERLIN', + 'state': 'WI', + 'zipCode': '53151', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 0, + 'occupancy': 'None', + 'startDate': '2016-02-11T00:00:00', + 'endDate': '2016-02-11T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 2676618, + 'line1': '1234 MAPLE ST', + 'line2': '', + 'line3': '', + 'city': 'MILWAUKEE', + 'state': 'WI', + 'zipCode': '53222', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 1234, + 'occupancy': 'None', + 'startDate': '2016-02-11T00:00:00', + 'endDate': '2016-12-14T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 3280018, + 'line1': '1234 TEST ST', + 'line2': '', + 'line3': '', + 'city': 'MILWAUKEE', + 'state': 'WI', + 'zipCode': '53222', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 1234, + 'occupancy': 'None', + 'startDate': '2016-12-14T00:00:00', + 'endDate': '0001-01-01T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 3644885, + 'line1': '5445 S Westrdige Dr', + 'line2': '', + 'line3': '', + 'city': 'New Berlin', + 'state': 'WI', + 'zipCode': '53151', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': false, + 'isInternational': false, + 'addressVerification': 5445, + 'occupancy': 'Other', + 'startDate': '0001-01-01T00:00:00', + 'endDate': '0001-01-01T00:00:00', + 'uspsVerified': false + }, { + 'addressId': 3644887, + 'line1': '9515 W National Ave', + 'line2': '', + 'line3': '', + 'city': 'West Allis', + 'state': 'WI', + 'zipCode': '53227', + 'country': 'US', + 'countyName': '', + 'addressType': 'Home', + 'isPrimary': true, + 'isInternational': false, + 'addressVerification': 9515, + 'occupancy': 'None', + 'startDate': '0001-01-01T00:00:00', + 'endDate': '0001-01-01T00:00:00', + 'uspsVerified': false + }], + 'email1': 'lcu@landmarkcu.com', + 'email2': '', + 'phone': [ + { + 'phoneKey': 'ApexApiWrapper.Apex.MemberServices.PhoneKey', + 'isPrimaryForType': false, + 'isInternational': false, + 'countryCode': '', + 'areaCode': '262', + 'phoneNumber': '796-4502', + 'extension': '0' + }, { + 'phoneKey': 'ApexApiWrapper.Apex.MemberServices.PhoneKey', + 'isPrimaryForType': false, + 'isInternational': false, + 'countryCode': '', + 'areaCode': '262', + 'phoneNumber': '796-4500', + 'extension': '0' + }, { + 'phoneKey': 'ApexApiWrapper.Apex.MemberServices.PhoneKey', + 'isPrimaryForType': true, + 'isInternational': false, + 'countryCode': '', + 'areaCode': '262', + 'phoneNumber': '796-4501', + 'extension': '0' + }], + 'status': 'Active', + 'isHomeOwner': false, + 'isHeadOfHouseHold': false, + 'language': '' +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/vehicle-credit-application.mock.ts b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/vehicle-credit-application.mock.ts new file mode 100644 index 0000000..c5ba6c4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/app/testing/shared-mocks/vehicle-credit-application.mock.ts @@ -0,0 +1,218 @@ +import { LoanTypeSet, LoanType } from '../../shared/loan-type'; +import { CreditApplicationValues } from '../../shared/credit-application-values'; +import { CreditApplication } from '../../shared/credit-application'; +import { LoanPurpose } from 'src/app/shared/loan-type/loan-purpose'; +import { ApiStatus } from 'src/app/shared/api-models/api-status'; +import { ApiErrorCode } from 'src/app/shared/api-models/api-error'; +import { PhoneTypeEnum } from 'src/app/shared/api-models/api-enums'; +import { VehicleType } from 'src/app/shared/vehicle-type/vehicle-type'; + + +export const vehicleCreditApplication: CreditApplication = { + 'loanType': LoanTypeSet[7], + 'note': 'This is a note!', + 'primaryContactMethod': '262-623-8990', + 'hasBeenCompleted': false, + 'disclosureInformation': { + 'truthful': true, + 'contact': true, + 'date': new Date() + }, + 'coApplicantLivesInMaritalPropertyState': true, + 'fullInfoBorrowers': [ + { + 'addressInformation': { + 'addressLine1': '123 Main St', + 'addressLine2': '', + 'zipcode': '53202', + 'city': 'Milwaukee', + 'state': CreditApplicationValues.STATE_REQUIRED_COAPP + }, + 'residenceInformation': { + 'isMailingAddressSame': true, + 'residenceMonthlyPayment': 500, + 'residenceTimeYears': '8', + 'residenceTimeMonths': '5', + 'residenceStatus': '0' + }, + 'personalInformation': { + 'firstName': 'Primary', + 'middleInitial': 'J', + 'lastName': 'Borrower', + 'dateOfBirth': '01/01/2010', + 'socialSecurityNumber': '666-55-5444', + 'primaryPhoneNumberType': PhoneTypeEnum.Home, + 'primaryPhoneNumber': '(555) 555-0134', + 'secondaryPhoneNumberType': null, + 'secondaryPhoneNumber': null, + 'email': 'primary@example.com', + 'maritalStatus': CreditApplicationValues.CHOICE_YES + }, + 'genderInformation': { + 'female': null, + 'male': true, + 'notProvided': null, + }, + 'ethnicityInformation': { + 'hispanicOrLatino': true, + 'mexican': null, + 'puertoRican': null, + 'cuban': null, + 'otherHispanicLatino': null, + 'otherHispanicLatinoText': null, + 'notHispanicOrLatino': null, + 'notProvided': null + }, + 'raceInformation': { + 'americanIndianAlaskaNative': null, + 'americanIndianAlaskaNativeText': null, + 'blackOrAfricanAmerican': null, + 'asian': null, + 'asianIndian': null, + 'chinese': null, + 'filipino': null, + 'japanese': null, + 'korean': null, + 'vietnamese': null, + 'otherAsian': true, + 'otherAsianText': 'Thai', + 'hawaiian': null, + 'nativeHawaiian': null, + 'guamanianChamorro': null, + 'samoan': null, + 'otherPacificIslander': null, + 'otherPacificIslanderText': null, + 'white': null, + 'notProvided': null + }, + 'employmentInformation': [{ + 'employerName': 'Petes Purple Pies', + 'employmentType': 'FullTime', + 'jobTitle': 'Lead Pie Prepper', + 'salary': 100000, + 'schedule': 'Annually', + 'employmentLengthYears': 10, + 'employmentLengthMonths': 0, + 'weeklyHours': null, + 'workPhoneNumber': '' + }] + }, { + 'addressInformation': { + 'addressLine1': '123 Main St', + 'addressLine2': '', + 'zipcode': '53202', + 'city': 'Milwaukee', + 'state': CreditApplicationValues.STATE_REQUIRED_COAPP + }, + 'residenceInformation': { + 'isMailingAddressSame': true, + 'residenceMonthlyPayment': 500, + 'residenceTimeYears': '8', + 'residenceTimeMonths': '5', + 'residenceStatus': '0' + }, + 'personalInformation': { + 'firstName': 'Spouse', + 'middleInitial': 'R', + 'lastName': 'Borrower', + 'dateOfBirth': '01/01/2009', + 'socialSecurityNumber': '666-33-3222', + 'primaryPhoneNumberType': PhoneTypeEnum.Home, + 'primaryPhoneNumber': '(555) 555-0199', + 'secondaryPhoneNumberType': null, + 'secondaryPhoneNumber': null, + 'email': 'coapp@example.org', + 'maritalStatus': CreditApplicationValues.CHOICE_YES + }, + 'genderInformation': { + 'female': null, + 'male': null, + 'notProvided': true + }, + 'ethnicityInformation': { + 'hispanicOrLatino': null, + 'mexican': null, + 'puertoRican': null, + 'cuban': null, + 'otherHispanicLatino': null, + 'otherHispanicLatinoText': null, + 'notHispanicOrLatino': null, + 'notProvided': null + }, + 'raceInformation': { + 'americanIndianAlaskaNative': null, + 'americanIndianAlaskaNativeText': null, + 'blackOrAfricanAmerican': null, + 'asian': null, + 'asianIndian': null, + 'chinese': null, + 'filipino': null, + 'japanese': null, + 'korean': null, + 'vietnamese': null, + 'otherAsian': null, + 'otherAsianText': null, + 'hawaiian': null, + 'nativeHawaiian': null, + 'guamanianChamorro': null, + 'samoan': null, + 'otherPacificIslander': null, + 'otherPacificIslanderText': null, + 'white': null, + 'notProvided': null + }, + 'otherIncomeInformation': [{ + 'amount': 100000, + 'incomeName': 'Bingo', + 'frequency': '' + }, + { + 'amount': 2000, + 'incomeName': 'Federal Check', + 'frequency': '' + }, + { + 'amount': 1000, + 'incomeName': 'Financial Aid', + 'frequency': '' + }] + } + ], + 'vehicleInformation': { + 'value': 1234, + 'condition': 'New', + 'type': VehicleType.AUTO, + 'year': 1999, + 'make': 'chevrolet', + 'model': 'camaro', + 'mileage': 23222, + 'term': '24 Months', + 'loanPurpose': 'Purchase', + 'seller' : 'Private Pary' + }, + 'purposeOfLoan': LoanPurpose.Purchase, + 'termOfLoan': '60', + 'hasCoApplicant': CreditApplicationValues.CHOICE_YES, + 'limitRequested': 25000, + 'confirmJointCredit': CreditApplicationValues.CHOICE_YES, + 'hasCoApplicantSpouse': CreditApplicationValues.CHOICE_YES, + 'status': ApiStatus.IN_PROGRESS, + 'page': { + 'hasAddress': true, + 'hasEmployment': true, + 'hasPersonal': true, + 'hasMemberInfo': true, + 'hasSpouseInfo': true, + 'hasTattleSpouseInfo': true, + 'hasResidenceInfo': true, + 'hasVehicleCollateral': true, + 'hasHMDA': true, + 'hasDisclosure': true, + 'hasCollateralInfo': true, + 'hasNote': true + }, + 'error': { + 'code': ApiErrorCode.No_Error, + 'message': 'No Error' + } +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/.gitkeep b/LCU.WebApps.External.OnlineLoanApp/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/LM_Full-Logo_Main.png b/LCU.WebApps.External.OnlineLoanApp/src/assets/LM_Full-Logo_Main.png new file mode 100644 index 0000000000000000000000000000000000000000..2aa6b0f933306cb2a3904f6347faccbabdff2811 GIT binary patch literal 7863 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Ⓢrp9 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 BtDgV> literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/cc-images/LCU_student.png b/LCU.WebApps.External.OnlineLoanApp/src/assets/cc-images/LCU_student.png new file mode 100644 index 0000000000000000000000000000000000000000..e6848e7a8ca88776263fc6c43db3a9d3b994bbac GIT binary patch literal 48825 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ⅅ8b52 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%0A(v4x{qK#CEJT10{&fM{^ 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%0D8#c2`9um;d3BYSsB3#ig*K*u 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 literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/eqo.png b/LCU.WebApps.External.OnlineLoanApp/src/assets/eqo.png new file mode 100644 index 0000000000000000000000000000000000000000..a71b0d7c4b4a8af01fd420c2724ec7a0c367e157 GIT binary patch literal 1112 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!pK&k)$%y41ia<&6%rQQIog&!K$p)4}>fj z<#(#>E_*5=m9@%lYj4)7W2?6W$7Z!nF8seWYghL4fK_S_ydtNZ`n>cXgVy@V#}=Uf3ll}U9U;(W2><0YS|h3kZzryHG^Y8@z<^SV0d zX4FB?yDQHW^g5<4T01@S+WF0UkJ?W6i7u5r*U}fNwjip1dg_iz^{Y->&VADVa(QI^ zT<+66;>v}pD^C|%<&?;_Y0G=4o-lQIta>G^Hg31~QRe9)SO5Emh%Bs6ne*zl{+*Kf zT36Sozg65Gv2>O6l1XQ(_7z3zf0lhOdA@a@%HD1JN^;lu7IH3K_3&`iOxC4FM?w#~ zM9*)R?00+a<+?EP`ag@~TfQcJo^`S8_LKZWg0rvpSll{M#B1~3zq#<``RrXfHq#1M z@X0P^2WrmyJnI&B>zU^!ch}6$+|#vDv(9~;QCzgC#`T1=zfR=-?Kr#3D*WP^qT_oR zx7vkvt$NKXX_dQo{%mf({MWU2+Z8we_?yC&2Pe|xiecQoFuP&qHU`SYxEwONLD z!d@1s-VfO{H~E`i>D_5Ul9K-23_Dw&d$8)AejAY{HzhOwb@av@+or20uC}GV&)m9f z+dkIQ&x$ATzP#0+`n~bV-Q2%%YchVha#`>EmUy%N`H^*YrTo+Szx%##mVV^N#Mf`+UT=z;>UnMQ-@DI_@xCtSHJQ9-`r*4_um5lO zcaP0^)3!JDJHyuOUYj~;YweEGt^azyZrJLx>E6xxJ3n21A3b>ue|xo^*~wKK)Fc0H z0NMjKf1=JLSCFMZ&gslei^_D%HlDeEhD|IYp^+7n$esSrOEde!|}1A)luD|5%&Ny|#QH_ri<1Pq?8elF{r5}E+;P70m? literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/files/IMPORTANT TERMS OF OUR HOME EQUITY LINE OF CREDIT.pdf b/LCU.WebApps.External.OnlineLoanApp/src/assets/files/IMPORTANT TERMS OF OUR HOME EQUITY LINE OF CREDIT.pdf new file mode 100644 index 0000000000000000000000000000000000000000..84f0465dc8cf9bd2f034032939593afd5375743b GIT binary patch literal 295121 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

=Z-#`_&6X_-tN*iqtws!v0 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

WGB#BKURb02pR69-Ck{J_AM((n83I!7q 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 SD4 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

465@EeuFg>%O0*v^O|vIXPE9fkC!1U>N@P9 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 Sf`?vFMq`04G&Y586#)Rt+CDr0 literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/i18n/en.json b/LCU.WebApps.External.OnlineLoanApp/src/assets/i18n/en.json new file mode 100644 index 0000000..cb57fb2 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/assets/i18n/en.json @@ -0,0 +1,1044 @@ +{ + "BUTTONS": { + "NEXT": "Next", + "BACK": "Previous", + "YES": "Yes", + "NO": "No", + "LANDMARK": "Go To Landmark Home", + "SUMMARY_NEXT": "Process Application", + "CONFIRM_DISCLOSURE": "Confirm Disclosures" + }, + "PAGETITLES": { + "COMMON": { + "INTRO": "", + "PERSONAL_INFO": "Personal Information", + "PRIMARYSPOUSE_PERSONAL_INFO": "Your Spouse's Personal Information", + "COAPP_PERSONAL_INFO": "Co-Applicant Personal Information", + "COAPPSPOUSE_PERSONAL_INFO": "Co-Applicant Spouse's Personal Information", + "ADDRESS_INFO": "Residence Information", + "PRIMARYSPOUSE_ADDRESS_INFO": "Your Spouse's Residence Information", + "COAPP_ADDRESS_INFO": "Co-Applicant Residence Information", + "COAPPSPOUSE_ADDRESS_INFO": "Co-Applicant Spouse's Residence Information", + "PRIMARY_SPOUSETYPE": "Spouse Applicant Type", + "COAPP_SPOUSETYPE": "Co-Applicant Spouse Applicant Type", + "PRIMARY_TATTLESPOUSE": "Spouse Information", + "COAPP_TATTLESPOUSE": "Co-Applicant Spouse Information", + "EMPLOYMENT_INFO": "Income Information", + "PRIMARYSPOUSE_EMPLOYMENT_INFO": "Your Spouse's Income Information", + "COAPP_EMPLOYMENT_INFO": "Co-Applicant Income Information", + "COAPPSPOUSE_EMPLOYMENT_INFO": "Co-Applicant Spouse's Income Information", + "DISCLOSURES": "Disclosures", + "SUMMARY": "Final Review", + "DECISION": "", + "PROCESSING": "" + }, + "EQUITY": { + "HMDA": "Home Mortgage Disclosure Act", + "COAPP_HMDA": "Co-Applicant Home Mortgage Disclosure Act", + "COLLATERAL": "Collateral Information" + }, + "VEHICLE": { + "COLLATERAL": "Vehicle Information" + } + }, + "FORMS": { + "GENERAL": { + "ANNUAL": "(Annual)", + "SELECTDEFAULT": "Choose One", + "OPTIONAL": "Optional" + }, + "FINANCIAL": { + "ALIMONY_FLAG": "I am paying child support", + "CHILD_SUPPORT": "Child support amount", + "OTHER_INCOME_FLAG": "I have other income", + "MORE_INCOME_FLAG": "I have more income", + "OTHERNAME": "Other income source", + "OTHERAMOUNT": "Other income", + "EMPLOYMENT_FLAG": "I have employment income", + "MORE_EMPLOYMENT_FLAG": "I have more employment income", + "PLACEHOLDERS": { + "OTHERNAME": "Annual income source", + "OTHERAMOUNT": "ex: 1,000", + "CHILD_SUPPORT": "ex: 1,000" + } + }, + "EMPLOYMENT": { + "NAME": "Employer name", + "TITLE": "Job/Position title", + "TYPE": "Employment Status", + "HOURS": "Hours per week", + "SCHEDULE": "Schedule", + "SALARY": "Amount", + "SALARY_HINT": "Hourly Rate / Gross Annual Income", + "LENGTH_OF_EMPLOYMENT": "Time at Employer", + "YEARS": "Years", + "MONTHS": "Months", + "PHONE": "Work Phone ", + "PLACEHOLDERS": { + "NAME": "Employer name", + "TITLE": "Job/Position title", + "HOURS": "ex: 25", + "SALARY": "ex: 1,000", + "PHONE": "(___) ___-____" + } + }, + "RESIDENCE": { + "ADDR1": "Address Line 1", + "ADDR2": "Address Line 2", + "CITY": "City", + "STATE": "State", + "ZIPCODE": "Zip Code", + "MAILINGSAME": "Mailing Address is the same as Current Address", + "TIME_AT_ADDRESS": "Time at Address", + "MONTHS": "Months", + "YEARS": "Years", + "STATUS": "Residence Status", + "PAYMENT": "Monthly Payment", + "PLACEHOLDERS": { + "CITY": "City", + "STATE": "State", + "ZIPCODE": "Zip Code", + "ADDR1": "Address", + "ADDR2": "Suite, Apartment, etc (Optional)", + "PAYMENT": "Rent/payment per month" + } + }, + "PERSONAL": { + "MARRIAGE": "Are you married?", + "IS_CO_APPLICANT_SPOUSE": "Is your spouse your co-applicant?", + "FNAME": "First Name", + "MIDDLE": "Middle Initial", + "LNAME": "Last Name", + "SUFFIX": "Suffix", + "DOB": "Date Of Birth", + "SSN": "Social Security Number", + "SSN_HINT": "Required to Run a Credit Report", + "PRIMARYPHONE": "Primary Phone #", + "PRIMARYPHONETYPE": "Type", + "SECONDARYPHONE": "Secondary Phone #", + "SECONDARYPHONETYPE": "Type", + "EMAIL": "Email Address", + "EMAIL_ESIGNATURE": "Email Address(es) for Electronic Signature Documents", + "PRIMARY_APPLICANT_EMAIL": "Primary Applicant Email Address", + "CO_APPLICANT_EMAIL": "Co-Applicant Email Address (if applicable)", + "PLACEHOLDERS": { + "FNAME": "First Name", + "MIDDLE": "Middle Initial", + "LNAME": "Last Name", + "SUFFIX": "Suffix", + "DOB": "MM/DD/YYYY", + "SSN": "___-__-____", + "PHONE": "(___) ___-____", + "EMAIL": "_@_._" + } + }, + "MEMBER": { + "APPLICANT_TYPE": "Are you applying with another person?", + "CONFIRM_JOINT_CREDIT": "Do you intend to apply for joint credit?", + "LIVES_IN_STATE": "Do you live in Wisconsin?", + "CO_APP_LIVES_IN_STATE": "Does your Co-Applicant live in Wisconsin?", + "CO_APPLICANT_HAS_SPOUSE": "Is your co-applicant married?", + "ASSOCIATION": "Association", + "EQUITY": { + "TERM": "Term", + "PURPOSE": "Purpose of Loan", + "CLIENTMESSAGE" : "Please describe your COVID-19 hardship" + }, + "PLACEHOLDERS": { + "LIMIT_REQUEST" : "ex: 1,000", + "CLIENTMESSAGE" : "Please describe your COVID-19 hardship" + }, + "DISCLOSURE": "Please review our ", + "ACCOUNT_REQUIRED": "If you are not currently a member of Landmark Credit Union and your application is approved, you will be required to become a member by opening a savings account. Please read Landmark’s eligibility for membership requirements here before you proceed.", + "AGREEMENT_REQUIREMENT": "

I have read and agree to Landmark's membership eligibility requirements.
", + "COOKIES": "This loan application uses cookies. By continuing, you agree that we may use cookies to complete your loan application session and monitor key performance metrics. You may change cookie settings for your browser, but please note, this may prevent the application from functioning correctly." + }, + "COLLATERAL": { + "OCCUPANCY": "Occupancy", + "ESTIMATEDVALUE": "Estimated Value", + "PROPERTYTYPE": "Property Type", + "LIENPOSITION": { + "FIRSTMORTGAGE" : "Does this property have a first mortgage?", + "SECONDMORTGAGE" : "Does this property have a second mortgage or HELOC?", + "PAYOFF" : "Payoff with this loan" + + }, + "IMPROVED": "Is this property currently being remodeled or improved?", + "LISTED": "Is this property currently listed for sale?", + "ON_RENTED_LOT" : "Is the home on a Rented lot?", + "RENT_PAID": "Rent paid per month", + "ANNUAL_PROPERTY_TAX": "Annual Property Taxes", + "PLACEHOLDERS": { + "ESTIMATEDVALUE": "ex: 1,000" + } + }, + "SAME_ADDRESS": { + "CO_APP": "Home Address - Same as yours?", + "HELOC": "Is this address the same as your address where you reside?", + "SPOUSE": "Spouse Address - Same as yours?" + }, + "VEHICLE": { + "TYPE": "Vehicle type", + "TYPES": { + "AUTOMOBILE": "Automobile", + "MOTORCYCLE": "Motorcycle", + "ATV": "ATV", + "SNOWMOBILE": "Snowmobile", + "PWC": "Personal Water Craft", + "RV": "Recreational Vehicle", + "BOAT": "Boat", + "TRAILER": "TrailerMotorHome" + }, + "CONDITION": "Vehicle condition", + "CONDITIONS": { + "NEW": "New", + "USED": "Used" + }, + "BOAT_TRAILER_TYPE": { + "SINGLE": "Single", + "TANDEM": "Tandem", + "TRI_AXLE": "Tri-Axle" + }, + "BOAT_MOTOR_TYPE": { + "INBOARD": "Inboard", + "OUTBOARD": "Outboard", + "INBOARD_OUTBOARD": "Inboard/Outboard" + }, + "VALUE": "Vehicle Estimated Value or Sale Price", + "YEAR": "Vehicle model year", + "MAKE": "Vehicle make", + "MODEL": "Vehicle model", + "MILEAGE": "Vehicle mileage", + "TERM": "Term", + "LOAN_PURPOSE": "Vehicle Loan Purpose", + "REFINANCE": "Refinance", + "PURCHASE": "Purchase", + "CASHOUT_REFINANCE": "Cash Out Refinance", + "LEASE": "Lease", + "PAYOFF": "Payoff", + "SELLER": "Vehicle seller", + "PRIVATE_PARTY": "Private party", + "DEALERSHIP": "Dealership", + "VIN": "Vehicle VIN Number", + "VIN_POWERSPORT": "Vehicle Serial / Hull / VIN number", + "TRANSFERRING": "Are you transferring plates from another vehicle?", + "STATE_TITLED": "State to be titled", + "COUNTY_TITLED": "County to be titled", + "TITLE_BRANDED": "Is the title branded?", + "LIEN": "Is there a lien?", + "LICENSE_PLATE_NUMBER": "License Plate Number", + "DEALER_NAME": "Dealership Name", + "DEALER_PHONE": "Dealership Phone #", + "MUNICIPALITY_WHERE_KEPT": "Municipality where the vehicle will be kept", + "BOAT_VALUE": "Boat Estimated Value or Sale Price", + "BOAT_YEAR": "Boat model year", + "BOAT_MAKE": "Boat make", + "BOAT_MODEL": "Boat model", + "BOAT_HULL": "Boat hull number", + "BOAT_LENGTH": "Length of boat", + "BOAT_LOAN_PURPOSE": "Boat Loan Purpose", + "BOAT_SAME_ADDRESS": "Is the boat kept at the same address where you reside?", + "BOAT_SELLER": "Boat Seller", + "TRAILER_YEAR": "Trailer model year", + "TRAILER_MAKE": "Trailer make", + "TRAILER_TYPE": "Trailer type", + "MOTOR_YEAR": "Motor model year", + "MOTOR_MAKE": "Motor make", + "MOTOR_MODEL": "Motor model", + "MOTOR_TYPE": "Motor type", + "MUNICIPALITY_TYPE": "Municipality Type where the vehicle will be kept", + "PLACEHOLDERS": { + "VALUE": "Value or Sale Price", + "YEAR": "Model Year", + "MAKE": "Make", + "MODEL": "Model", + "MILEAGE": "Mileage", + "VIN": "VIN Number", + "VIN_POWERSPORT": "Serial / Hull / VIN Number", + "COUNTY_TITLED": "County", + "BOAT_LENGTH": "Length in Feet", + "BOAT_HULL": "Hull Number" + } + }, + "SPOUSETYPE": { + "PRIMARY": "Do you want to provide income/credit history for your spouse to qualify for this loan?", + "COAPP": "Do you want to provide income/credit history for you co-applicant's spouse to qualify for this loan?" + }, + "SUMMARY": { + "NOTE": "Please share any additional information you wish regarding your loan application.", + "PLACEHOLDERS": { + "NOTE": "" + } + } + }, + "HEADINGS": { + "ADDITIONAL_EMPLOYER_A": "", + "ADDITIONAL_EMPLOYER_B": "Previous employer", + "ADDITIONAL_RESIDENCE_A": "", + "ADDITIONAL_RESIDENCE_B": "Previous address", + "COLLATERAL": "{{borrowerName}}, tell us about your collateral", + "VEHICLE": "{{borrowerName}}, tell us about the vehicle you want to finance", + "DECISION": "Application status", + "PROCESSING": "We are processing your application", + "GET_STARTED": "Getting started is easy", + "FINANCIAL": { + "SIGNING_APPLICANT": "{{borrowerName}}, tell us about your income", + "APPLICANT_SPOUSE": "Tell us about {{borrowerName}}'s income" + }, + "PERSONAL": { + "PRIMARY_BORROWER" : "Tell us about yourself", + "CO_BORROWER" : "Co-Applicant, tell us about yourself", + "PRIMARY_BORROWER_SPOUSE": "Tell us about your spouse", + "CO_BORROWER_SPOUSE" : "Tell us about your spouse" + }, + "RESIDENCE": { + "SIGNING_APPLICANT": "{{borrowerName}}, tell us about your residence", + "APPLICANT_SPOUSE": "Tell us about {{borrowerName}}'s residence" + }, + "DISCLOSURES": "Disclosures", + "HMDA": { + "HMDA": "HMDA", + "ETHNICITY": { + "PRIMARY_BORROWER": "Applicant Ethnicity", + "CO_BORROWER": "Co-Applicant Ethnicity" + }, + "RACE": { + "PRIMARY_BORROWER": "Applicant Race", + "CO_BORROWER": "Co-Applicant Race" + }, + "SEX": { + "PRIMARY_BORROWER": "Applicant Sex", + "CO_BORROWER": "Co-Applicant Sex" + } + }, + "SPOUSE": { + "PRIMARY_BORROWER": "Tell us about your spouse", + "PRIMARY_BORROWER_SPOUSE": "Tell us about your spouse", + "CO_BORROWER": "Tell us about your spouse", + "CO_BORROWER_SPOUSE" : "Tell us about your spouse", + "PERSONAL": "Tell us about your spouse", + "RESIDENCE": "Tell us about your co-applicant's spouse's residence" + }, + "COBORROWER": { + "PERSONAL": "Tell us about your spouse/co-applicant", + "RESIDENCE": "Tell us about your spouse/co-applicant's residence" + } + }, + "ERRORS": { + "CUSTOM": { + "CC_LIMIT": "You must enter a dollar value between 100 and 999,999.", + "ZIPCODE": "Zip code is a 5 digit numeric zipcode", + "SSN": "SSN must be in the form 123-45-6789", + "PHONE": "Phone must be in the form (123) 456-7890", + "NOTE": "Can only contain characters: .,&@-;!', and be less than 3500 characters", + "HMDA": "Cannot select both male and female, please only select one", + "ALPHAONLY": "{{title}} may have no numbers, spaces or special characters.", + "SAME_VALUE": "{{title}} cannot be the same as {{value.name}}.", + "TITLE_BRANDED": "*Vehicles with a branded title are not eligible for loans.", + "BOAT_TITLE_BRANDED": "*Boats with a branded title are not eligible for loans.", + "LIEN": "*Seller will need to be at closing to sign documents." + }, + "REQUIRED": "{{title}} is required.", + "MINLENGTH": "{{title}} must be at least {{value}} characters.", + "MINLOANAMT": "The minimum loan amount is {{value}}.", + "MAXLOANAMT": "The maximum loan amount is {{value}}.", + "MININCAMT": "The minimum income amount is {{value}}.", + "MAXINCAMT": "The maximum income amount is {{value}}.", + "MAX_AMOUNT_CURRENCY": "The maximum {{title}} amount is {{value}}.", + "MAXLENGTH": "{{title}} must not have more than {{value}} characters.", + "MAXVAL": "{{title}} must not be more than {{value}}.", + "MINVAL": "{{title}} must be at least {{value}}.", + "EMAIL": "{{title}} must be a valid email address.", + "PATTERN": "{{title}} is in an invalid format.", + "REQUIREDSTATE": "State must be {{value}}.", + "REQUIRE_WI_COLLATERAL": "Please note Real Estate used for Home Equity loan collateral must be located in Wisconsin", + "TITLE": { + "PRIMARY_CONTACT": "Primary Contact Method", + "NOTE": "Additional Information", + "SAME_ADDRESS": "Is Address the Same?", + "DISCLOSURE": "Contact disclosure", + "EMPLOYMENT_LENGTH_YEARS": "Length of employment (years)", + "EMPLOYMENT_LENGTH_MONTHS": "Length of employment (months)", + "OTHER_INCOME_SOURCE": "Other annual income source", + "OTHER_ANNUAL_INCOME": "Other annual income", + "FIRST_MORTGAGE": "First Mortgage", + "SECOND_MORTGAGE": "Second Mortgage", + "IMPROVED": "Is the property being improved", + "ON_RENTED_LOT": "Is the home on rented lot or land you own", + "APPLICANT_TYPE": "Applicant Type", + "JOINT_CREDIT": "Confirm joint credit", + "MARTIAL_PROPERTY": "Lives in Marital Property State", + "CO_APP_MARTIAL_PROPERTY": "Co-applicant lives in Marital Property State", + "SPOUSE_TYPE": "Spouse Type", + "VEHICLE_VALUE": "Vehicle value", + "BOAT_VALUE": "Boat value", + "CLIENTMESSAGE": "Client Comment" + } + }, + "TIMEOUT":{ + "TITLE": "Application timed out", + "REASON": "Due to inactivity, your session has timed out and is no longer active.", + "INFO": "To create a new loan application, use the button below to navigate back to the home page.", + "INFO_ADMIN": "Use the button below to navigate back to the admin home page.", + "BUTTON": "Go back home", + "POPUP": { + "TITLE": "Session expire warning", + "EXPIRE": "Your session will expire in {{time}} seconds.", + "QUESTION": "Do you wish to extend the session?", + "BUTTON_HOME": "Go back to home page", + "BUTTON_EXTEND": "Extend session" + } + }, + "COPY": { + "TYPE_MESSAGE": { + "CREDITCARD_CASHBACK": "Cash Back VISA® Card", + "CREDITCARD_REWARDS": "Rewards VISA® Card", + "CREDITCARD_STUDENT": "Student Rewards VISA® Card", + "CREDITCARD_SECURE": "Secured VISA® Card", + "EQUITY_HELOC_FLEX": "Home Equity Line of Credit", + "EQUITY_HELOC_FIXED": "Fixed Term Home Equity Loan", + "EQUITY_HELOC_RATE_MOD": "Home Equity Line of Credit Rate Modification", + "EQUITY_MORTGAGE_REF_EXPRESS": "Mortgage Refi Express", + "VEHICLE_AUTO": "Auto/Motorcycle Loan", + "EQUITY_MANUF_HOME": "Manufactured Home Loan", + "VEHICLE_PRE_APPROVAL": "Auto/Motorcycle Pre-Approval", + "VEHICLE_BOAT": "Boat Loan", + "VEHICLE_POWERSPORT": "Powersports Loan", + "VEHICLE_MOTORHOME": "Trailer/Motorhome Loan", + "SIGNATURE_LOAN": "Personal Signature Loan", + "SPECIAL_LOAN": "COVID-19 Loan" + }, + "MEMBER": { + "CREDIT_CARD": "Thank you for applying for a {{typeMessage}}. This application takes approximately 10-15 minutes to complete. Please make sure you have enough time to fill out the form before beginning. You will not be able to go back once you leave.", + "EQUITY_LOAN": "Thank you for applying for a {{typeMessage}}. This application takes approximately 10-15 minutes to complete. Please make sure you have enough time to fill out the form before beginning. You will not be able to go back once you leave.", + "CYCLE_LOAN": "Thank you for applying for an {{typeMessage}}. This application takes approximately 10-15 minutes to complete. Please make sure you have enough time to fill out the form before beginning. You will not be able to go back once you leave.", + "SPECIAL_LOAN": "Thank you for applying for a {{typeMessage}}. The Application takes approximately 10-15 minutes to complete. Please make sure you have enough time to fill out the form before you begin. You will not be able to get back in the application once you leave.

This loan program is available to qualified borrowers with a COVID-19-related financial hardship. Only Applicants who became members of Landmark no later than March 17, 2020 may be eligible. Borrower must be a current member of Landmark, cannot currently be in a bankruptcy proceeding or have any written off loans with Landmark Credit Union.  Loans and rates are subject to credit approval. Consumer loan rates based upon qualified credit.  Other terms and rates are available; some restrictions may apply. Rates and terms are subject to change.

This loan is available only to members who are experiencing a financial hardship related to COVID-19; we will ask you to describe the hardship as a loan eligibility criterion.", + "HELOC_OR_FIXED_EQUITY_LOAN":"Thank you for applying for a {{typeMessage}}. This application takes approximately 10-15 minutes to complete. Please make sure you have enough time to fill out the form before beginning. You will not be able to go back once you leave." + }, + "DISCLOSURE":{ + "CC_REWARDS_CASHBACK": "
Introductory offer and bonus not available to current Landmark Cash Back or Rewards consumer cardholders or previous holders of Landmark Cash Bank or Rewards accounts that were closed within six (6) months of the new application date. Offer available to existing Starter Card, Secured Card, or Student Card holders who qualify for Cash Back or Rewards Card. Existing primary or joint holders of Landmark credit card accounts may only have one (1) such account at any time.

" + }, + "PERSONAL": { + "PRIMARY_BORROWER" : "", + "CO_BORROWER" : "", + "PRIMARY_BORROWER_SPOUSE": "", + "CO_BORROWER_SPOUSE" : "" + }, + "RESIDENCE": { + "PRIMARY_BORROWER" : "", + "CO_BORROWER" : "", + "PRIMARY_BORROWER_SPOUSE": "", + "CO_BORROWER_SPOUSE" : "" + }, + "HMDA" : "Home Mortgage Disclosure Act", + "COLLATERAL": { + "PRIMARY_BORROWER": "Tell us about your collateral." + }, + "EMPLOYMENT": { + "PRIMARY_BORROWER" : "", + "CO_BORROWER" : "", + "PRIMARY_BORROWER_SPOUSE": "", + "CO_BORROWER_SPOUSE" : "", + "OTHER_INCOME_BSA": "Alimony, child support or separate maintenance Income need not be revealed if you do not wish to have it considered for repaying this loan.", + "OTHER_INCOME_BSA_CC": "Alimony, child support or separate maintenance Income need not be revealed if you do not wish to have it considered for repaying this obligation." + }, + "SPOUSE": { + "PRIMARY_BORROWER": "You can provide minumum or maximum information for you spouse.", + "CO_BORROWER" : "You can provide minimum or maximium information for your co-applicant's spouse.", + "PRIMARY_BORROWER_SPOUSE": "I UNDERSTAND THAT LANDMARK IS REQUIRED BY WISCONSIN LAW TO NOTIFY MY SPOUSE BY MAIL IF MY CREDIT CARD OR LOAN IS GRANTED. I certify that the credit being applied for, if granted, will be incurred or obtained during marriage and will be in the interest of the marriage or family. This statement is made in accordance with Wis.Stat.sec.766.55 (1).", + "CO_BORROWER_SPOUSE" : "Tell us how we can contact your co-applicant's spouse about your joint credit application." + }, + "DISCLOSURES": "For purposes of the disclosures below, the words \"I,\" \"me,\" \"we,\" \"our\" and \"my\" refer to each of the applicants individually and together; \"Landmark\" refers to Landmark Credit Union.

This application does not constitute a contract for extension of credit.

", + "SUMMARY": "", + "PROCESSING": "{{firstName}}, your application is being processed. The process shouldn't take more than a couple minutes. If you experience any issues, please contact our help number at {{phoneNumber}}. Thank you for your patience and we appreciate you choosing Landmark Credit Union.", + "DECISION_TITLE": "{{firstName}}, you've been approved", + "DECISION": "Congratulations! All applications are subject to final review and verification by our underwriters. In the meantime though, please check out our recommended next steps below.", + "TCPA": "By providing your telephone number, including your wireless telephone number, in connection with this account or loan, you have consented and authorized Landmark Credit Union or anyone acting on its behalf to contact you about your account or loan at that number using an automatic telephone dialing system or an artificial or prerecorded message." + }, + "SUMMARY": { + "GENERAL": { + "CREDIT_CARD_LIMIT": "Limit requested", + "LOAN_LIMIT": "Loan amount", + "LOAN_TERM": "Loan term", + "LOAN_TITLE": "Your new loan", + "CREDIT_CARD_TITLE": "Your new credit card", + "HELOC_RATE_MOD": "Please note that all borrowers under the original loan documents must execute the modification agreement", + "PURPOSE": "Purpose of Loan" + }, + "COLLATERAL": { + "ADDRESS_SAME": "Address: Same as primary?", + "ESTIMATED_VALUE": "Estimated Value", + "OCCUPANCY_TYPE": "Occupancy Type", + "PROPERTY_TYPE": "Property Type", + "FIRST_MORTGAGE" : "Does this property have a first mortgage?", + "SECOND_MORTGAGE" : "Does this property have a second mortgage or HELOC?", + "PAYOFF_FIRST_MORTGAGE" : "Payoff first mortgage with this loan?", + "PAYOFF_SECOND_MORTGAGE" : "Payoff second mortgage with this loan?", + "IS_BEING_IMPROVED": "Is this property currently being remodeled or improved?", + "IS_LISTED": "Is this property currently listed for sale?", + "ON_RENTED_LOT": "Is the home on a rented lot?", + "ANNUAL_PROPERTY_TAX": "Annual Property Taxes", + "RENT_PAID": "Rent paid per month" + }, + "VEHICLE": { + "TYPE": "Vehicle type", + "CONDITION": "Vehicle condition", + "VALUE": "Vehicle Estimated Value or Sale Price", + "MODEL": "Vehicle model", + "MILEAGE": "Vehicle mileage", + "TERM": "Term", + "SELLER": "Vehicle seller", + "BOAT_ADDRESS": "Boat address" + }, + "PRIMARY_HEADING": "Let's review your information", + "NOTE_HEADING": "Did we forget something important?", + "PRIMARY_SPOUSE_HEADING": "Please review your spouse's information", + "CO_APP_HEADING": "Please review your co-applicant's information", + "CO_APP_SPOUSE_HEADING": "Please review you co-applicant's spouse information", + "ADDITIONAL_INFO" : "Additional Information", + "EMPLOYER": { + "CURRENT": "Current Employer", + "OTHER": "Other Current Employer", + "PREVIOUS": "Previous Employer", + "EMPLOYERNAME": "Employer", + "EMPLOYERTYPE": "Employment Type", + "SALARY": "Salary", + "SCHEDULE": "Schedule", + "LENGTH": "Length", + "WORKPHONE": "Work Phone", + "OTHERINCOME": "Other Income", + "SOURCE": "Source", + "CHILDSUPPORT": "Child Support" + }, + "CHANGE_LINK": "change »", + "TITLES": { + "TATTLE": "Spouse's personal details", + "VEHICLE": "Your Vehicle", + "RESIDENCE": "Your current residence information", + "RESIDENCE_PRIOR": "Your prior residence information", + "INCOME": "Your income information", + "PERSONAL": "Your personal details", + "SPOUSE_DETAILS": "Your spouse's personal details", + "SPOUSE_RESIDENCE": "Your spouse's current residence information", + "SPOUSE_INCOME": "Your spouse's income information", + "COAPP_DETAILS": "Co-Applicant's personal details", + "COAPP_RESIDENCE": "Co-applicant's current residence information", + "COAPP_RESIDENCE_PRIOR": "Co-applicant's prior residence information", + "COAPP_INCOME": "Your co-applicant's income information", + "TATTLE_RESIDENCE": "Your co-applicant's spouse's current residence information", + "COAPP_SPOUSE_DETAILS": "Co-Applicant's spouse personal details", + "COAPP_SPOUSE_RESIDENCE": "Co-Applicant's spouse current residence information", + "COAPP_SPOUSE_INCOME": "Co-Applicant's spouse income information", + "COLLATERAL": "Your collateral information" + }, + "PERSONAL": { + "FULL_NAME": "Full Name", + "DOB": "Date of Birth", + "SSN": "Social Security Number", + "PRIMARY_PHONE": "Primary Phone", + "SECONDARY_PHONE": "Secondary Phone", + "EMAIL": "Email", + "PRIMARY_APPLICANT_EMAIL": "Primary Applicant Email Address", + "CO_APPLICANT_EMAIL": "Co-Applicant Email Address" + }, + "ADDRESS": { + "STREET_ADDRESS": "Address", + "TENANT_STATUS": "Tenancy", + "MONTHLY_PAYMENT": "Monthly Payment", + "LENGTH": "Duration of residence", + "MAILING_ADDRESS": "Mailing Address", + "ADDRESS_SAME": "Same address as spouse?" + }, + "ADDITIONAL": { + "PRIMARY_CONTACT": "Please select the best way to reach you", + "PROMO_CODE": "Promo Code", + "MOD_FEE": "Where to take the modification fee from?" + } + }, + "HMDA": { + "HMDA_INTRO": "The purpose of collecting this information is to help ensure that all applicants are treated fairly and that the housing needs of communities and neighborhoods are being fulfilled. For residential mortgage lending, Federal law requires that we ask applicants for their demographic information (ethnicity, race, and sex) in order to monitor our compliance with equal credit opportunity, fair housing, and home mortgage disclosure laws. You are not required to provide this information, but are encouraged to do so. You may select one or more designations for “Ethnicity” and one or more designations for “Race”. The law provides that we may not discriminate on the basis of this information, or on whether you choose to provide it. However, if you choose not to provide the information and you have made this application in person, Federal regulations require us to note your ethnicity, race, and sex on the basis of visual observation or surname. The law also provides that we may not discriminate on the basis of age or marital status information you provide in this application. If you do not wish to provide some or all of this information, please check below.", + "ETHNICITY": { + "HISPANIC_OR_LATINO": "Hispanic or Latino", + "MEXICAN": "Mexican", + "PUERTO_RICAN": "Puerto Rican", + "CUBAN": "Cuban", + "OTHER_HISPANIC_LATINO": "Other Hispanic or Latino", + "OTHER_HISPANIC_LATINO_TEXT": "Write origin, for example, Argentinean, Colombian, Dominican, Nicaraguan, Salvadoran, Spaniard, and so on:", + "NOT_HISPANIC_OR_LATINO": "Not Hispanic or Latino" + }, + "RACE": { + "AMERICAN_INDIAN_ALASKA_NATIVE": "American Indian or Alaska Native", + "AMERICAN_INDIAN_ALASKA_NATIVE_TEXT": "Write name of enrolled or principal tribe:", + "BLACK_OR_AFRICAN_AMERICAN": "Black or African American", + "ASIAN": "Asian", + "ASIAN_INDIAN": "Asian Indian", + "CHINESE": "Chinese", + "FILIPINO": "Filipino", + "JAPANESE": "Japanese", + "KOREAN": "Korean", + "VIETNAMESE": "Vietnamese", + "OTHER_ASIAN": "Other Asian", + "OTHER_ASIAN_TEXT": "Write race, for example, Hmong, Laotian, Thai,Pakistani, Cambodian and so on:", + "HAWAIIAN": "Hawaiian", + "NATIVE_HAWAIIAN": "Native Hawaiian", + "GUAMANIAN_CHAMORRO": "Guamanian or Chamorro", + "SAMOAN": "Samoan", + "OTHER_PACIFIC_ISLANDER": "Other Pacific Islander", + "OTHER_PACIFIC_ISLANDER_TEXT": "Write race, for example, Fijian, Tongan, and so on:", + "WHITE": "White" + }, + "SEX": { + "FEMALE": "Female", + "MALE": "Male" + }, + "DO_NOT_PROVIDE_INFO": "I do not wish to provide this information" + }, + "ERROR": { + "JOINT_CREDIT": { + "MESSAGE": "Your Application type will be changed to \"Individual\", if your answer to this question is \"No\".", + "TITLE": "Applying for Joint Credit", + "HEADLINE": "Error Message" + } + }, + "DISCLOSURE" : { + "CREDIT_CARD" : { + "TITLES": { + "SPOUSAL": "Notice to Married Applicant", + "ACCEPTANCE": "Acceptance Agreement" + }, + "SPOUSAL": "No provision of a marital property agreement, a unilateral statement under Wis. Stats. sec. 766.59 or a court decree under Wis. Stats. sec. 766.70 adversely affects the interests of Landmark unless prior to the time the credit is extended, Landmark is furnished with a copy of the agreement, statement or decree, or has actual knowledge of the adverse provision when the obligation to Landmark is incurred.

IF I HAVE INDICATED THAT I AM APPLYING FOR CREDIT SEPARATE FROM MY SPOUSE, THIS SECTION APPLIES TO ME. I UNDERSTAND THAT LANDMARK IS REQUIRED BY WISCONSIN LAW TO NOTIFY MY SPOUSE BY MAIL IF MY CREDIT CARD OR LOAN IS GRANTED. I certify that the credit being applied for, if granted, will be incurred or obtained during marriage and will be in the interest of the marriage or family. This statement is made in accordance with Wis.Stat.sec.766.55 (1). ", + "ACCEPTANCE": "I (we) certify that I (we) UNDERSTAND AND AGREE THAT, AS A CONDITION TO MY (OUR) ACCEPTANCE AND USE OF THE APPROVED CREDIT: 1.I (we) hereby grant to Landmark a security interest in all my (our) funds in my (our) present and future Landmark deposit accounts, except IRAs and any other accounts exempted by state and federal law, to the full amount of my credit indebtedness with Landmark; 2. If I (we) default under the terms of the credit agreement, Landmark may, at its sole option and subject to any notice and right to cure required by Wis.Stat.Sec.425.105: (i) exercise its right of set-off and apply the funds or any portion thereof toward payment of the outstanding indebtedness; and/or (ii) exercise its right to refuse to allow withdrawal from such accounts to the full extent of the delinquent amount, subject to the restrictions contained in Sec.1026.13 (d)(1) of federal Reg. Z.", + "TRUTHFUL": { + "PART1": "I (we) certify that the information provided in this application is truthful, accurate and voluntary, and authorize Landmark to investigate my (our) creditworthiness, credit history and financial responsibility through any credit bureau or by any other reasonable means at the time of underwriting this application and on an ongoing basis during the life of this loan or credit card with Landmark, including any collection proceedings.", + "PART2": "I (we) understand that it may be a Federal crime punishable by fine or imprisonment, or both, to knowingly make any false statements concerning any of the above facts, as applicable under the provisions of Title 18, United States Code, Section 1014.I agree to abide by the issuer's terms governing VISA accounts that will be provided to me in the event my application is approved. I/We have read the Terms and Conditions listed." + }, + "CONTACT": { + "PART1": "By providing a phone number, cellular phone number, or other wireless device phone number (\"Contact Number\") in connection with this application, you are expressly authorizing Landmark Credit Union to transmit, and consenting to receive, communications at that number from Landmark Credit Union and its agents regarding this application and any and all resulting accounts and/or loans held at Landmark Credit Union. These communications may include calls made using an automatic telephone dialing system or artificial or prerecorded voice messages for informational, account servicing, or debt collection purposes. If you provide a mobile telephone number, you agree that we may contact you at this number even if your mobile phone carrier charges you for the call." + } + }, + "HELOC" : { + "HEADER" : { + "REVIEW" : "Disclosures for Review" + }, + "TITLES" : { + "KNOW" : "WHAT YOU SHOULD KNOW ABOUT Home Equity Lines of Credit (HELOC)", + "CREDIT_DISCLOSURE" : "Home Equity Line of Credit Disclosure" + }, + "CONFIRM" : "To continue, confirm that you’ve reviewed the disclosures and that you acknowledge receipt" + }, + "ELECTRONIC": { + "PART1": "E-Signature and Electronic Disclosures Agreement by clicking here", + "PART2": ". By clicking the \"Next\" button below, you agree to this disclosure." + }, + "MODAL": { + "COPY": "The words \"we,\" \"us,\" and \"our\" refer to Landmark Credit Union, and the words \"you\" and \"your\" mean you, the the individual(s). As used in this Disclosure, \"Communication\" means any authorization, agreement, disclosure, notice, or other information related to your credit card/loan application, including but not limited to information that we are required by law to provide to you in writing.", + "HEADER": "This Disclosure applies to your loan/credit card application.", + "PRINT": "Print/Download", + "PART_1": { + "TITLE": "1. Scope of Communications to Be Provided in Electronic Form.", + "CONTENT": "When you proceed with the loan application, you agree that we may provide you with any Communications in electronic format, and that we may not send paper Communications to you, unless and until you withdraw your consent as described below. Your consent to receive electronic Communications includes, but is not limited to all legal and regulatory disclosures and communications associated with your application, loan and credit card, including Risk Based Pricing and Adverse Action Notices and all privacy policies and notices." + }, + "PART_2": { + "TITLE": "2. Method of Providing Communications to You in Electronic Form.", + "CONTENT": "All Communications that we provide to you in electronic form will be provided either (1) via e-mail, (2) by access to a website that we will designate in an e-mail notice we send to you at the time the information is available, or (3) to the extent permissible by law, by access to a website that we will generally designate in advance for such purpose." + }, + "PART_3": { + "TITLE": "3. How to Withdraw Consent.", + "CONTENT": "You may withdraw your consent to receive Communications in electronic form by contacting us at (262) 796-4500. At our option, we may treat your provision of an invalid e-mail address, or the subsequent malfunction of a previously valid e-mail address, as a withdrawal of your consent to receive electronic Communications. We will not impose a fee to process the withdrawal of your consent to receive electronic Communications. Any withdrawal of your consent to receive electronic Communications will be effective only after we have a reasonable period of time to process your withdrawal." + }, + "PART_4": { + "TITLE": "4. How to Update Your Records.", + "CONTENT": "It is your responsibility to provide us with true, accurate and complete e-mail address, contact, and other information related to this Disclosure and your application, loan or credit card, and to maintain and update promptly any changes in this information. You can update information (such as your e-mail address) by contacting us at (262) 796-4500." + }, + "PART_5": { + "TITLE": "5. Hardware and Software Requirements.", + "CONTENT": "In order to access, view, and retain electronic Communications that we make available to you, you must have:", + "BULLET_POINTS": { + "POINT_1": "an Internet browser that supports 128 bit encryption;", + "POINT_2": "sufficient electronic storage capacity on your computer's hard drive or other data storage unit;", + "POINT_3": "an e-mail account with an Internet service provider and e-mail software in order to participate in our electronic Communications programs;", + "POINT_4": "a personal computer (for PC's: Pentium 120 MHz or higher; for Macintosh, Power Mac 9500, Power PC 604 processor 120-MHz Base or higher), operating system and telecommunications connections to the Internet capable of receiving, accessing, displaying, and either printing or storing Communications received from us in electronic form via a plain text-formatted e-mail or by access to our web site using an internet browser." + } + }, + "PART_6": { + "TITLE": "6. Requesting Paper Copies.", + "CONTENT": "We will not be obligated to send you a paper copy of any Communication unless you request it or we otherwise deem it appropriate to do so. You can obtain a paper copy of an electronic Communication by printing it yourself or by requesting that we mail you a paper copy, provided that such request is made within a reasonable time after we first provided the electronic Communication to you. To request a paper copy, contact us by telephone at (262) 796-4500. We may charge you a reasonable service charge for the delivery of paper copies of any Communication provided to you electronically pursuant to this authorization. We reserve the right, but assume no obligation, to provide a paper (instead of electronic) copy of any Communication that you have authorized us to provide electronically." + }, + "PART_7": { + "TITLE": "7. Communications in Writing.", + "CONTENT": "All Communications in either electronic or paper format from us to you will be considered \"in writing.\" You should print or download for your records, a copy of this Disclosure and any other Communication that is important to you." + }, + "PART_8": { + "TITLE": "8. Federal Law.", + "CONTENT": "You acknowledge and agree that your consent to electronic Communications is being provided in connection with a transaction affecting interstate commerce that is subject to the federal Electronic Signatures in Global and National Commerce Act, and that you and we both intend that the Act apply to the fullest extent possible to validate our ability to conduct business with you by electronic means." + }, + "PART_9": { + "TITLE": "9. Termination/Changes.", + "CONTENT": "We reserve the right, at our sole discretion, to discontinue the provision of your electronic Communications, or to terminate or change the terms and conditions on which we provide electronic Communications. We will provide you with notice of any such termination or change as required by law." + } + } + }, + "ADDRESS_COMPARE": { + "HEADER": "Address Verification", + "UNABLE_TO_VERIFY": "We are unable to verify your address as entered, but found a close match.", + "CONFIRM": "Please confirm the correct address or edit your address above", + "UNABLE_TO_FIND_MATCH_LINE1": "We are unable to verify your address as entered.", + "UNABLE_TO_FIND_MATCH_LINE2": "Please confirm or edit your address above.", + "USE_MINE": "Use the address I provided", + "SELECT_MATCHING": "Use recommended address from USPS" + }, + "PATRIOT_ACT":{ + "HEADER": "US Patriot Act Notice", + "BODY": { + "PART1": "IMPORTANT INFORMATION ABOUT PROCEDURES FOR OPENING A NEW ACCOUNT OR CREDIT CARD", + "PART2": "To help the government fight the funding of terrorism and money laundering activities, federal law requires all financial institutions to obtain, verify, and record information that identifies each person who opens an account. ", + "PART3": "What this means for you: when you open an account or apply for credit, we will ask for your name, address, date of birth, and other information that will allow us to identify you. We may also ask to see your driver’s license or other identifying documents or require you to visit one of our branches and meet with an associate as a condition of credit approval. " + } + }, + "FOOTER":{ + "NCUA": "Federally Insured by NCUA", + "HOUSING": "Equal Housing Opportunity", + "SECURE": "Secure Site", + "COPY_RIGHT": "©{{currentYear}} Landmark Credit Union. All rights reserved.", + "ACCESSIBILITY": "Accessibility", + "SECURITY": "Security", + "TERMS": "Terms", + "PRIVACY": "Privacy" + }, + "HEADER": { + "ASSISTANCE": "Need assistance?", + "PHONE_NUM": "Call 262.796.4500", + "PROPER_PHONE_NUMBER": "+1-262-796-4500", + "LOAN_MESSAGE": { + "CREDITCARD_CASHBACK": "Cash Back VISA Card", + "CREDITCARD_REWARDS": "Rewards VISA Card", + "CREDITCARD_STUDENT": "Student Rewards VISA Card", + "CREDITCARD_SECURE": "Secured VISA Card", + "EQUITY_HELOC_FLEX": "Home Equity Line of Credit", + "EQUITY_HELOC_FIXED": "Fixed Term Home Equity Loan", + "EQUITY_HELOC_RATE_MOD": "Home Equity Line of Credit Rate Modification", + "EQUITY_MORTGAGE_REF_EXPRESS": "Mortgage Refi Express", + "VEHICLE_AUTO": "Auto/Motorcycle Loan", + "EQUITY_MANUF_HOME": "Manufactured Home Loan", + "VEHICLE_PRE_APPROVAL": "Auto/Motorcycle Pre-Approval Loan", + "VEHICLE_BOAT": "Boat Loan", + "VEHICLE_POWERSPORT": "Powersports Loan", + "VEHICLE_MOTORHOME": "Trailer/Motorhome Loan", + "SIGNATURE_LOAN": "Personal Signature Loan", + "SPECIAL_LOAN": "COVID-19 Loan" + } + }, + "DECISION": { + "APPROVED": { + "TITLE": "Your application is conditionally approved.", + "TEXT": "Your application has been conditionally approved. An associate will review your file and reach out to you within the next business day to discuss next steps. Final approval and terms are subject to change.", + "TEXT_CC": "Your application has been conditionally approved. An associate will review your file and reach out to you within the next business day to discuss next steps. Final approval and terms are subject to change." + }, + "REVIEW": { + "TITLE": "Thank you for applying with Landmark Credit Union.", + "TEXT": "Your application is being processed. A representative will respond to you within one business day.", + "TEXT_CC": "Your application is being processed. Thank you for submitting your credit card application." + }, + "DENIED": { + "TITLE": "Thank you for applying with Landmark Credit Union.", + "TEXT": "We have processed your application and are unable to honor your request at this time. A notice will be mailed to you indicating the reason for this decision. You may then want to contact Landmark Credit Union if you have further questions about your application.", + "TEXT_CC": "We have processed your application and are unable to honor your request at this time. A notice will be mailed to you indicating the reason for this decision. You may then want to contact Landmark Credit Union if you have further questions about your application." + }, + "ERROR_SUBMIT": { + "TITLE": "Thank you for applying with Landmark Credit Union.", + "TEXT": "Your application is being processed. A representative will respond to you within one business day.", + "TEXT_CC": "Your application is being processed. Thank you for submitting your credit card application." + }, + "ERROR_DECISION": { + "TITLE": "Thank you for applying with Landmark Credit Union.", + "TEXT": "Your application is being processed. A representative will respond to you within one business day.", + "TEXT_CC": "Your application is being processed. Thank you for submitting your credit card application." + } + }, + "LANDING": { + "TITLE": "Start a new loan application", + "CREDIT_CARD": "Select a credit card type", + "HOME_EQUITY": "Select a home equity type", + "VEHICLE": "Select a vehicle type", + "PERSONAL": "Select a personal type", + "START": "Start Application", + "BACK": "Back To All Loans", + "LOADING": "Loading Existing Loans", + "LOADING_LOAN": "Loading Loan. Please Wait.", + "COMPLETE_LOANS": "Complete Your Loans:", + "NO_LOANS": "You do not have any pending loan applications.", + "LOAN_ERROR": "Unable to provide pending loans at this time.", + "VEHICLE_BACK": "Back To All Vehicle Loans", + "VIEW_TYPES": "View All Types", + "TYPE":{ + "CREDIT": "Credit Card", + "HOME": "Home Equity", + "VEHICLE": "Vehicle", + "PERSONAL": "Personal", + "SPECIAL": "COVID-19 Loan" + }, + "SUB_TYPE":{ + "CASHBACK": "Cash Back VISA", + "REWARDS": "Rewards VISA", + "STUDENT": "Student Rewards VISA", + "SECURED": "Secured VISA", + "HELOC": "HELOC", + "HOME_FIXED": "Home Equity Fixed", + "MORT_REFI": "Mortgage Refi Express", + "MANUF": "Manufactured Home", + "PERSONAL": "Personal Signature", + "AUTO": "Auto/Motorcycle", + "VEHICLE_PRE": "Apply for Pre-Approval", + "VEHICLE_LOAN": "Apply for Loan", + "BOAT": "Boat", + "POWER": "Powersports", + "TRAILER": "Trailer/Motorhome", + "HELOC_RATE_MOD": "HELOC Rate Modification" + } + }, + "MEMBER":{ + "LOGIN": { + "USERNAME": "User Id", + "PASSWORD": "Password", + "LOGIN": "Login", + "MEMBER_HEADER": "Landmark Member", + "SUB_TEXT": "Log in to continue", + "ERROR_LOCKED": "Your account has been locked due to multiple invalid login attempts. Please contact us to resolve.", + "ERROR_USER": "The User ID and / or Password does not match our records. Try again.", + "ERROR_COOKIES": "Unable to authenticate. Turn on cookies.", + "ERROR_SERVER": "Server not responding. Try again.", + "NON_MEMBER_HEADER": "Let's get started.", + "MEMBER_SUBTEXT": "Sign in for a faster application.", + "NON_MEMBER_SUBTEXT": "Welcome! Apply for a loan in minutes.", + "STARTED": "Apply" + }, + "VERIFICATION": { + "SSN": "Last 4 Digits of SSN", + "BIRTH": "Date of Birth", + "ATTEMPT": "Attempt", + "OF": "Of", + "HEADER": "Member Verification", + "ERROR": "The SSN and / or DOB does not match our records. Try again.", + "FAILURE": "Something went wrong and we can't verify your identity at this time.", + "SUBTEXT": "Please share the following details.", + "CONTINUE": "Continue", + "CONTINUE_NO_VERIFY": "Continue Without Verification", + "MAX_ATTEMPTS": "You exceeded the max attempts." + } + }, + "UNAVAILABLE": { + "HEADER": "Whoops...", + "BODY_TOP": "It looks like this page is unavailable.", + "BODY_BOTTOM": "Please try again later." + }, + "DOWN": { + "HEADER": "System Maintenance", + "BODY_TOP": "We are conducting system maintenance and during this time our loan applications will be unavailable. We apologize for any inconvenience this may cause.", + "SAVE": "Return to home" + }, + "BRANCHMAP": { + "TITLE": "Branch Mapping Administration.", + "SUBTEXT": "You can use this utility to preview, download, and upload a branch mapping configuration.", + "DATETEXT": "The data below was uploaded on: ", + "DOWNLOAD": "Click below to download the current branch mapping as a CSV file.", + "MAPPING_ERROR": "The server rejected the mapping.", + "LOAD_ERROR": "Failed to load branch config" + }, + "BRANCHNAMEMAP": { + "TITLE": "Branch Name Mapping Administration.", + "SUBTEXT": "You can use this utility to view and update the branch name mapping configuration using the mappings from CRIF.", + "DATETEXT": "The data below was uploaded on: ", + "UPDATETEXT": "Click below to update the current branch name mapping with data from CRIF.", + "LOAD_ERROR": "Failed to load branch name config" + }, + "CONFIG": { + "TITLE": "Start/Stop Loan Applications", + "SUBTEXT": "Manage access to online loan applications for website maintenance, security reasons or any other reason.", + "APP_DISABLED": "Application Disabled", + "APP_ENABLED": "Application Enabled", + "SAVE": "Save and Publish", + "STATUS": "Current Status", + "LOAD_ERROR": "Failed to load stored settings", + "UPDATE_ERROR": "Failed to update stored settings" + }, + "ADMIN": { + "LANDING_TITLE": "Select an admin feature", + "LOAN_TABLE": "Loan Management", + "BRANCH_CONFIG": "Branch Mapping", + "BRANCHNAME_CONFIG": "Branch Name Mapping", + "SCHUMER": "Schumer Preview", + "NAVIGATE": "Navigate", + "HOME": "Admin Home", + "DATE_ERROR": "MM/DD/YYYY", + "SYSTEM": "System Config", + "LOAD_ERROR": "Failed to load loans", + "DELETE_ERROR": "Failed to delete loan", + "SUBMIT_ERROR": "Failed to load loans" + + }, + "BEFOREYOUBEGIN": { + "CREDITCARD_STUDENT": { + "INTRO": "Before you begin the process, you should have the following documents to be uploaded. Please note that uploading requested documents will expedite processing your loan request.", + "HEADINGS": [ + { + "HEADING": "Documents to Upload", + "VALUES": [ + "Proof of income (i.e. most recent payroll check stub, 1040s for self-employed borrowers, bank statement)." + ] + } + ] + }, + "EQUITY_HELOC_FLEX": { + "INTRO": "Before you begin the process, you should be prepared with the following information and documents to be uploaded. Please note that uploading requested documents will expedite processing your loan request.", + "HEADINGS": [ + { + "HEADING": "Information", + "VALUES": ["Real estate used for home equity loan collateral must be located in Wisconsin."] + }, + { + "HEADING": "Documents to Upload", + "VALUES": [ + "Proof of homeowner insurance.", + "If loan amount is greater than $100,000, proof of income (i.e. most recent payroll check stub, 1040s for self-employed borrows, bank statement).", + "A copy of the first mortgage balance statement." + ] + } + ] + }, + "EQUITY_HELOC_FIXED": { + "INTRO": "Before you begin the process, you should be prepared with the following information and documents to be uploaded. Please note that uploading requested documents will expedite processing your loan request.", + "HEADINGS": [ + { + "HEADING": "Information", + "VALUES": ["Real estate used for home equity loan collateral must be located in Wisconsin."] + }, + { + "HEADING": "Documents to Upload", + "VALUES": [ + "Proof of homeowner insurance.", + "Proof of income (i.e. last two years of W2s and a recent payroll check stub, or 1040s for self-employed borrowers, or a bank statement).", + "A copy of the first mortgage balance statement." + ] + } + ] + }, + "EQUITY_MANUFACTURED": { + "INTRO": "Before you begin the process, you should be prepared with the following information and documents to be uploaded. Please note that uploading requested documents will expedite processing your loan request.", + "HEADINGS": [ + { + "HEADING": "Information", + "VALUES": ["Real estate used for home equity loan collateral must be located in Wisconsin."] + }, + { + "HEADING": "Documents to Upload", + "VALUES": ["Manufactured home title or purchase contract."] + } + ] + }, + "EQUITY_MORTGAGE_REF_EXPRESS": { + "INTRO": "Before you begin the process, you should be prepared with the following information and documents to be uploaded. Please note that uploading requested documents will expedite processing your loan request.", + "HEADINGS": [ + { + "HEADING": "Information", + "VALUES": [ + "Real estate used for home equity loan collateral must be located in Wisconsin.", + "This loan is only eligible to refinance existing mortgages on your primary residence.
  1. If you would like to cash out against the equity of your property, or the property is not your primary residence, please use the Home Equity Fixed loan application.
  2. If you prefer a home equity line of credit, please use the HELOC loan application.
" + ] + }, + { + "HEADING": "Documents to Upload", + "VALUES": [ + "Proof of homeowner insurance.", + "Proof of income (i.e. last two years of W2s and a recent payroll check stub, or 1040s for self-employed borrowers, or a bank statement)." + ] + } + ] + }, + "EQUITY_HELOC_RATE_MOD": { + "INTRO": "Before you begin the process, you should be prepared with the following information.", + "HEADINGS": [ + { + "HEADING": "Information", + "VALUES": [ + "Landmark Credit Union will need to pull the credit report for the primary applicant only.", + "Please note that all borrowers under the original loan documents must execute the modification agreement.", + "If you are requesting an increase to an existing line of credit, please use the HELOC loan application." + ] + } + ] + }, + "VEHICLE_AUTO": { + "INTRO": "Before you begin the process, you should be prepared with the following information and documents to be uploaded. Please note that uploading requested documents will expedite processing your loan request.", + "HEADINGS": [ + { + "HEADING": "Information", + "VALUES": [ + "If working with a dealership, you will need to provide the dealership name and phone number.", + "If the vehicle is currently titled in any state other than Wisconsin, a copy of the title may be required.", + "If there is a lien, a 10 day payoff with per diem will be requested." + ] + }, + { + "HEADING": "Documents to Upload", + "VALUES": ["If working with a dealership, a copy of the purchase contract."] + } + ] + }, + "VEHICLE_BOAT": { + "INTRO": "Before you begin the process, you should be prepared with the following information and documents to be uploaded. Please note that uploading requested documents will expedite processing your loan request.", + "HEADINGS": [ + { + "HEADING": "Information", + "VALUES": [ + "If working with a dealership, you will need to provide the dealership name and phone number.", + "If the boat is currently titled in any state other than Wisconsin, a copy of the title may be required.", + "If there is a lien, a 10 day payoff with per diem will be requested.", + "The boat information, and if applicable, motor and trailer information." + ] + }, + { + "HEADING": "Documents to Upload", + "VALUES": [ + "If working with a dealership, a copy of the purchase contract.", + "A copy of the registration card from DNR.", + "Title(s) needed at closing." + ] + } + ] + }, + "VEHICLE_POWERSPORT": { + "INTRO": "Before you begin the process, you should be prepared with the following information and documents to be uploaded. Please note that uploading requested documents will expedite processing your loan request.", + "HEADINGS": [ + { + "HEADING": "Information", + "VALUES": [ + "If working with a dealership, you will need to provide the dealership name and phone number.", + "If the vehicle is currently titled in any state other than Wisconsin, a copy of the title may be required.", + "If there is a lien, a 10 day payoff with per diem will be requested." + ] + }, + { + "HEADING": "Documents to Upload", + "VALUES": ["If working with a dealership, a copy of the purchase contract."] + } + ] + }, + "VEHICLE_MOTORHOME": { + "INTRO": "Before you begin the process, you should be prepared with the following information and documents to be uploaded. Please note that uploading requested documents will expedite processing your loan request.", + "HEADINGS": [ + { + "HEADING": "Information", + "VALUES": [ + "If working with a dealership, you will need to provide the dealership name and phone number.", + "If the vehicle is currently titled in any other state than Wisconsin, a copy of the title may be required.", + "If there is a lien, a 10 day payoff with per diem will be requested." + ] + }, + { + "HEADING": "Documents to Upload", + "VALUES": ["If working with a dealership, a copy of the purchase contract."] + } + ] + } + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/loading.gif b/LCU.WebApps.External.OnlineLoanApp/src/assets/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..e9bb5255fc43dee66747ce01a48bdf8bb6665c95 GIT binary patch literal 62694 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 literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/settings/default_settings.json b/LCU.WebApps.External.OnlineLoanApp/src/assets/settings/default_settings.json new file mode 100644 index 0000000..2bbf20f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/assets/settings/default_settings.json @@ -0,0 +1,2324 @@ +{ + "configuration": { + "lastUpdated": "2019-04-25T13:46:05.119Z", + "lastUpdatedBy": "gfrick", + "applicationDisabled": false, + "id": "758f64a9-0e3e-41a1-8b09-e3c37b27437b" + }, + "mfdHomeAndRvByAmount": { + "0": [ + 48 + ], + "10000": [ + 48, + 60, + 72, + 84 + ], + "20000": [ + 48, + 60, + 72, + 84, + 120, + 180 + ] + }, + "homeEquity": [ + 60, + 84, + 120, + 180, + 240 + ], + "special": [ + 36 + ], + "mortgageRefiExpress": [ + 96, + 120, + 144 + ], + "vehiclePreApproval": [ + 36, + 48, + 63, + 75 + ], + "termsByYear": { + "2023": [ + 36, + 48, + 63, + 75, + 84 + ], + "2022": [ + 36, + 48, + 63, + 75 + ], + "2021": [ + 36, + 48, + 63 + ], + "2020": [ + 36, + 48 + ] + }, + "boatLoansByAmount": { + "0": [ + 48 + ], + "10000": [ + 48, + 63, + 72, + 84 + ], + "20000": [ + 48, + 63, + 72, + 84, + 120, + 180 + ] + }, + "powersportByAmount": { + "0": [ + 36, + 48, + 63 + ], + "10000": [ + 36, + 48, + 63, + 72 + ] + }, + "signatureByAmount": { + "0": [ + 12, + 24, + 36 + ], + "5000": [ + 48 + ] + }, + "typePurposes": { + "CC_CashBack": [ + "Other", + "Consolidation", + "Reestablish_Credit" + ], + "CC_Rewards": [ + "Other", + "Consolidation", + "Reestablish_Credit" + ], + "CC_Student": [ + "Other", + "Consolidation", + "Reestablish_Credit" + ], + "CC_Secure": [ + "Other", + "Consolidation", + "Reestablish_Credit" + ], + "EQ_HELOC_Flex": [ + "Home_Improvement", + "Home_Purchase", + "Home_Refinance", + "Other", + "Cash_Out_Refinance" + ], + "EQ_Mtg_Refi_Express": [ + "Home_Improvement", + "Home_Purchase", + "Home_Refinance", + "Other", + "Cash_Out_Refinance" + ], + "EQ_Home_Equity_Fixed": [ + "Home_Improvement", + "Home_Purchase", + "Home_Refinance", + "Other", + "Cash_Out_Refinance" + ], + "EQ_Mfg_Home_With_or_Without_Land": [ + "Home_Improvement", + "Purchase_Manufactured_Home", + "Home_Refinance", + "Other", + "Cash_Out_Refinance" + ], + "V_Vehicle": [ + "Purchase", + "Cash_Out_Refinance", + "Refinance" + ], + "V_VehiclePreapproval": [ + "Purchase", + "Cash_Out_Refinance", + "Refinance" + ], + "M_Boat": [ + "Purchase", + "Refinance" + ], + "P_Powersport": [ + "Purchase", + "Refinance" + ], + "T_TrailerMotorhome": [ + "Purchase", + "Refinance" + ], + "S_Signature": [ + "Other", + "Consolidation" + ], + "S_Special": [ + "Other", + "Consolidation" + ] + }, + "limits": { + "EQ_HELOC_Flex": { + "min": 10000.0, + "max": 500000.0 + }, + "EQ_Mtg_Refi_Express": { + "min": 10000.0, + "max": 250000.0 + }, + "EQ_Home_Equity_Fixed": { + "min": 10000.0, + "max": 500000.0 + }, + "EQ_Mfg_Home_With_or_Without_Land": { + "min": 250.0, + "max": 250000.0 + }, + "V_Vehicle": { + "min": 250.0, + "max": 250000.0 + }, + "V_VehiclePreapproval": { + "min": 250.0, + "max": 250000.0 + }, + "M_Boat": { + "min": 250.0, + "max": 250000.0 + }, + "P_Powersport": { + "min": 250.0, + "max": 250000.0 + }, + "T_TrailerMotorhome": { + "min": 250.0, + "max": 250000.0 + }, + "S_Signature": { + "min": 250.0, + "max": 25000.0 + }, + "S_Special": { + "min": 250.0, + "max": 10000.0 + } + }, + "statesRequiringSpouseInformation": [ + "WI" + ], + "stateOptions": [ + "AA", + "AE", + "AK", + "AL", + "AP", + "AR", + "AS", + "AZ", + "CA", + "CO", + "CT", + "DC", + "DE", + "FL", + "GA", + "GU", + "HI", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "LA", + "MA", + "MD", + "ME", + "MI", + "MN", + "MO", + "MP", + "MS", + "MT", + "NC", + "ND", + "NE", + "NH", + "NJ", + "NM", + "NV", + "NY", + "OH", + "OK", + "OR", + "PA", + "PR", + "PW", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VA", + "VI", + "VT", + "WA", + "WI", + "WV", + "WY" + ], + "incomeLimits": { + "Hourly": { + "min": 0.0, + "max": 999.0 + }, + "Annually": { + "min": 0.0, + "max": 1000000000.0 + } + }, + "branchMapping": { + "branchCodesByZip": [ + { + "zipcode": "53001", + "channelId": 1561599 + }, + { + "zipcode": "53002", + "channelId": 1561599 + }, + { + "zipcode": "53003", + "channelId": 1561245 + }, + { + "zipcode": "53004", + "channelId": 1561591 + }, + { + "zipcode": "53005", + "channelId": 1561255 + }, + { + "zipcode": "53006", + "channelId": 1561657 + }, + { + "zipcode": "53007", + "channelId": 1561255 + }, + { + "zipcode": "53008", + "channelId": 1561255 + }, + { + "zipcode": "53010", + "channelId": 1561599 + }, + { + "zipcode": "53011", + "channelId": 1561599 + }, + { + "zipcode": "53012", + "channelId": 1561591 + }, + { + "zipcode": "53013", + "channelId": 1561591 + }, + { + "zipcode": "53014", + "channelId": 1561657 + }, + { + "zipcode": "53015", + "channelId": 1561241 + }, + { + "zipcode": "53016", + "channelId": 1561665 + }, + { + "zipcode": "53017", + "channelId": 1561664 + }, + { + "zipcode": "53018", + "channelId": 1561267 + }, + { + "zipcode": "53019", + "channelId": 1561599 + }, + { + "zipcode": "53020", + "channelId": 1561599 + }, + { + "zipcode": "53021", + "channelId": 1561604 + }, + { + "zipcode": "53022", + "channelId": 1561288 + }, + { + "zipcode": "53023", + "channelId": 1561288 + }, + { + "zipcode": "53024", + "channelId": 1561591 + }, + { + "zipcode": "53026", + "channelId": 1561591 + }, + { + "zipcode": "53027", + "channelId": 1561662 + }, + { + "zipcode": "53029", + "channelId": 1561267 + }, + { + "zipcode": "53031", + "channelId": 1561599 + }, + { + "zipcode": "53032", + "channelId": 1561657 + }, + { + "zipcode": "53033", + "channelId": 1561664 + }, + { + "zipcode": "53034", + "channelId": 1561665 + }, + { + "zipcode": "53035", + "channelId": 1561657 + }, + { + "zipcode": "53036", + "channelId": 1561284 + }, + { + "zipcode": "53037", + "channelId": 1561604 + }, + { + "zipcode": "53038", + "channelId": 1561284 + }, + { + "zipcode": "53039", + "channelId": 1561665 + }, + { + "zipcode": "53040", + "channelId": 1561599 + }, + { + "zipcode": "53042", + "channelId": 1561599 + }, + { + "zipcode": "53044", + "channelId": 1561599 + }, + { + "zipcode": "53045", + "channelId": 1561588 + }, + { + "zipcode": "53046", + "channelId": 1561265 + }, + { + "zipcode": "53047", + "channelId": 1561284 + }, + { + "zipcode": "53048", + "channelId": 1561662 + }, + { + "zipcode": "53049", + "channelId": 1561657 + }, + { + "zipcode": "53050", + "channelId": 1561657 + }, + { + "zipcode": "53051", + "channelId": 1561288 + }, + { + "zipcode": "53052", + "channelId": 1561288 + }, + { + "zipcode": "53056", + "channelId": 1561267 + }, + { + "zipcode": "53057", + "channelId": 1561599 + }, + { + "zipcode": "53058", + "channelId": 1561267 + }, + { + "zipcode": "53059", + "channelId": 1561662 + }, + { + "zipcode": "53060", + "channelId": 1561599 + }, + { + "zipcode": "53061", + "channelId": 1561599 + }, + { + "zipcode": "53062", + "channelId": 1561599 + }, + { + "zipcode": "53063", + "channelId": 1561599 + }, + { + "zipcode": "53064", + "channelId": 1561267 + }, + { + "zipcode": "53065", + "channelId": 1561657 + }, + { + "zipcode": "53066", + "channelId": 1561245 + }, + { + "zipcode": "53069", + "channelId": 1561267 + }, + { + "zipcode": "53070", + "channelId": 1561591 + }, + { + "zipcode": "53072", + "channelId": 1561262 + }, + { + "zipcode": "53073", + "channelId": 1561599 + }, + { + "zipcode": "53074", + "channelId": 1561591 + }, + { + "zipcode": "53075", + "channelId": 1561599 + }, + { + "zipcode": "53076", + "channelId": 1561664 + }, + { + "zipcode": "53078", + "channelId": 1561662 + }, + { + "zipcode": "53079", + "channelId": 1561599 + }, + { + "zipcode": "53080", + "channelId": 1561591 + }, + { + "zipcode": "53081", + "channelId": 1561591 + }, + { + "zipcode": "53082", + "channelId": 1561604 + }, + { + "zipcode": "53083", + "channelId": 1561599 + }, + { + "zipcode": "53085", + "channelId": 1561599 + }, + { + "zipcode": "53086", + "channelId": 1561662 + }, + { + "zipcode": "53088", + "channelId": 1561657 + }, + { + "zipcode": "53089", + "channelId": 1561265 + }, + { + "zipcode": "53090", + "channelId": 1561599 + }, + { + "zipcode": "53091", + "channelId": 1561657 + }, + { + "zipcode": "53092", + "channelId": 1561591 + }, + { + "zipcode": "53093", + "channelId": 1561599 + }, + { + "zipcode": "53094", + "channelId": 1561284 + }, + { + "zipcode": "53095", + "channelId": 1561604 + }, + { + "zipcode": "53096", + "channelId": 1561599 + }, + { + "zipcode": "53097", + "channelId": 1561591 + }, + { + "zipcode": "53098", + "channelId": 1561284 + }, + { + "zipcode": "53099", + "channelId": 1561662 + }, + { + "zipcode": "53101", + "channelId": 1561631 + }, + { + "zipcode": "53102", + "channelId": 1561631 + }, + { + "zipcode": "53103", + "channelId": 2081492 + }, + { + "zipcode": "53104", + "channelId": 1561624 + }, + { + "zipcode": "53105", + "channelId": 1561631 + }, + { + "zipcode": "53108", + "channelId": 1561598 + }, + { + "zipcode": "53109", + "channelId": 1561631 + }, + { + "zipcode": "53110", + "channelId": 1561616 + }, + { + "zipcode": "53114", + "channelId": 1561631 + }, + { + "zipcode": "53115", + "channelId": 1561631 + }, + { + "zipcode": "53118", + "channelId": 1561245 + }, + { + "zipcode": "53119", + "channelId": 1561245 + }, + { + "zipcode": "53120", + "channelId": 1561631 + }, + { + "zipcode": "53121", + "channelId": 1561631 + }, + { + "zipcode": "53122", + "channelId": 1561588 + }, + { + "zipcode": "53125", + "channelId": 1561631 + }, + { + "zipcode": "53126", + "channelId": 1561624 + }, + { + "zipcode": "53127", + "channelId": 1561262 + }, + { + "zipcode": "53128", + "channelId": 1561631 + }, + { + "zipcode": "53129", + "channelId": 1561289 + }, + { + "zipcode": "53130", + "channelId": 1561289 + }, + { + "zipcode": "53132", + "channelId": 1561289 + }, + { + "zipcode": "53137", + "channelId": 1561284 + }, + { + "zipcode": "53138", + "channelId": 1561661 + }, + { + "zipcode": "53139", + "channelId": 1561631 + }, + { + "zipcode": "53140", + "channelId": 1561639 + }, + { + "zipcode": "53141", + "channelId": 1561639 + }, + { + "zipcode": "53142", + "channelId": 1561639 + }, + { + "zipcode": "53143", + "channelId": 1561639 + }, + { + "zipcode": "53144", + "channelId": 1561639 + }, + { + "zipcode": "53146", + "channelId": 1561243 + }, + { + "zipcode": "53147", + "channelId": 1561631 + }, + { + "zipcode": "53148", + "channelId": 1561631 + }, + { + "zipcode": "53149", + "channelId": 2081492 + }, + { + "zipcode": "53150", + "channelId": 2081492 + }, + { + "zipcode": "53151", + "channelId": 1561243 + }, + { + "zipcode": "53152", + "channelId": 1561631 + }, + { + "zipcode": "53153", + "channelId": 1561262 + }, + { + "zipcode": "53154", + "channelId": 1561598 + }, + { + "zipcode": "53156", + "channelId": 1561245 + }, + { + "zipcode": "53157", + "channelId": 1561631 + }, + { + "zipcode": "53158", + "channelId": 1561639 + }, + { + "zipcode": "53159", + "channelId": 1561631 + }, + { + "zipcode": "53167", + "channelId": 1561631 + }, + { + "zipcode": "53168", + "channelId": 1561631 + }, + { + "zipcode": "53170", + "channelId": 1561631 + }, + { + "zipcode": "53171", + "channelId": 1561639 + }, + { + "zipcode": "53172", + "channelId": 1561598 + }, + { + "zipcode": "53176", + "channelId": 1561631 + }, + { + "zipcode": "53177", + "channelId": 1561624 + }, + { + "zipcode": "53178", + "channelId": 1561245 + }, + { + "zipcode": "53179", + "channelId": 1561631 + }, + { + "zipcode": "53181", + "channelId": 1561631 + }, + { + "zipcode": "53182", + "channelId": 1561624 + }, + { + "zipcode": "53183", + "channelId": 1561267 + }, + { + "zipcode": "53184", + "channelId": 1561631 + }, + { + "zipcode": "53185", + "channelId": 1561631 + }, + { + "zipcode": "53186", + "channelId": 1561262 + }, + { + "zipcode": "53187", + "channelId": 1561262 + }, + { + "zipcode": "53188", + "channelId": 1561262 + }, + { + "zipcode": "53189", + "channelId": 1561262 + }, + { + "zipcode": "53190", + "channelId": 1561245 + }, + { + "zipcode": "53191", + "channelId": 1561631 + }, + { + "zipcode": "53192", + "channelId": 1561631 + }, + { + "zipcode": "53194", + "channelId": 1561624 + }, + { + "zipcode": "53195", + "channelId": 1561631 + }, + { + "zipcode": "53199", + "channelId": 1561631 + }, + { + "zipcode": "53201", + "channelId": 1561241 + }, + { + "zipcode": "53202", + "channelId": 1561615 + }, + { + "zipcode": "53203", + "channelId": 1561241 + }, + { + "zipcode": "53204", + "channelId": 1561241 + }, + { + "zipcode": "53205", + "channelId": 1561241 + }, + { + "zipcode": "53206", + "channelId": 1561241 + }, + { + "zipcode": "53207", + "channelId": 1561615 + }, + { + "zipcode": "53208", + "channelId": 1561241 + }, + { + "zipcode": "53209", + "channelId": 1561255 + }, + { + "zipcode": "53210", + "channelId": 1561241 + }, + { + "zipcode": "53211", + "channelId": 1561615 + }, + { + "zipcode": "53212", + "channelId": 1561241 + }, + { + "zipcode": "53213", + "channelId": 1561241 + }, + { + "zipcode": "53214", + "channelId": 1561587 + }, + { + "zipcode": "53215", + "channelId": 1561283 + }, + { + "zipcode": "53216", + "channelId": 1561241 + }, + { + "zipcode": "53217", + "channelId": 1561255 + }, + { + "zipcode": "53218", + "channelId": 1561255 + }, + { + "zipcode": "53219", + "channelId": 1561241 + }, + { + "zipcode": "53220", + "channelId": 1561283 + }, + { + "zipcode": "53221", + "channelId": 1561283 + }, + { + "zipcode": "53222", + "channelId": 1561255 + }, + { + "zipcode": "53223", + "channelId": 1561255 + }, + { + "zipcode": "53224", + "channelId": 1561255 + }, + { + "zipcode": "53225", + "channelId": 1561255 + }, + { + "zipcode": "53226", + "channelId": 1561255 + }, + { + "zipcode": "53227", + "channelId": 1561587 + }, + { + "zipcode": "53228", + "channelId": 1561587 + }, + { + "zipcode": "53233", + "channelId": 1561241 + }, + { + "zipcode": "53234", + "channelId": 1561241 + }, + { + "zipcode": "53235", + "channelId": 1561616 + }, + { + "zipcode": "53237", + "channelId": 1561283 + }, + { + "zipcode": "53259", + "channelId": 1561615 + }, + { + "zipcode": "53263", + "channelId": 1561255 + }, + { + "zipcode": "53267", + "channelId": 1561241 + }, + { + "zipcode": "53268", + "channelId": 1561615 + }, + { + "zipcode": "53274", + "channelId": 1561283 + }, + { + "zipcode": "53278", + "channelId": 1561241 + }, + { + "zipcode": "53288", + "channelId": 1561615 + }, + { + "zipcode": "53290", + "channelId": 1561241 + }, + { + "zipcode": "53293", + "channelId": 1561241 + }, + { + "zipcode": "53295", + "channelId": 1561241 + }, + { + "zipcode": "53401", + "channelId": 1561639 + }, + { + "zipcode": "53402", + "channelId": 1561285 + }, + { + "zipcode": "53403", + "channelId": 1561639 + }, + { + "zipcode": "53404", + "channelId": 1561639 + }, + { + "zipcode": "53405", + "channelId": 1561285 + }, + { + "zipcode": "53406", + "channelId": 1561624 + }, + { + "zipcode": "53407", + "channelId": 1561639 + }, + { + "zipcode": "53408", + "channelId": 1561624 + }, + { + "zipcode": "53490", + "channelId": 1561285 + }, + { + "zipcode": "53501", + "channelId": 1561661 + }, + { + "zipcode": "53502", + "channelId": 1561661 + }, + { + "zipcode": "53503", + "channelId": 1561661 + }, + { + "zipcode": "53505", + "channelId": 1561631 + }, + { + "zipcode": "53506", + "channelId": 1561661 + }, + { + "zipcode": "53507", + "channelId": 1561661 + }, + { + "zipcode": "53508", + "channelId": 1561661 + }, + { + "zipcode": "53511", + "channelId": 1561661 + }, + { + "zipcode": "53512", + "channelId": 1561661 + }, + { + "zipcode": "53515", + "channelId": 1561661 + }, + { + "zipcode": "53517", + "channelId": 1561661 + }, + { + "zipcode": "53520", + "channelId": 1561661 + }, + { + "zipcode": "53521", + "channelId": 1561661 + }, + { + "zipcode": "53522", + "channelId": 1561661 + }, + { + "zipcode": "53523", + "channelId": 1561284 + }, + { + "zipcode": "53525", + "channelId": 1561661 + }, + { + "zipcode": "53526", + "channelId": 1561661 + }, + { + "zipcode": "53527", + "channelId": 1561661 + }, + { + "zipcode": "53528", + "channelId": 1561661 + }, + { + "zipcode": "53529", + "channelId": 1561661 + }, + { + "zipcode": "53531", + "channelId": 1561661 + }, + { + "zipcode": "53532", + "channelId": 1561661 + }, + { + "zipcode": "53533", + "channelId": 1561661 + }, + { + "zipcode": "53534", + "channelId": 1561661 + }, + { + "zipcode": "53535", + "channelId": 1561661 + }, + { + "zipcode": "53536", + "channelId": 1561661 + }, + { + "zipcode": "53537", + "channelId": 1561661 + }, + { + "zipcode": "53538", + "channelId": 1561284 + }, + { + "zipcode": "53542", + "channelId": 1561661 + }, + { + "zipcode": "53543", + "channelId": 1561661 + }, + { + "zipcode": "53544", + "channelId": 1561661 + }, + { + "zipcode": "53545", + "channelId": 1561661 + }, + { + "zipcode": "53546", + "channelId": 1561631 + }, + { + "zipcode": "53547", + "channelId": 1561661 + }, + { + "zipcode": "53548", + "channelId": 1561661 + }, + { + "zipcode": "53549", + "channelId": 1561284 + }, + { + "zipcode": "53550", + "channelId": 1561661 + }, + { + "zipcode": "53551", + "channelId": 1561284 + }, + { + "zipcode": "53553", + "channelId": 1561661 + }, + { + "zipcode": "53555", + "channelId": 1561661 + }, + { + "zipcode": "53557", + "channelId": 1561665 + }, + { + "zipcode": "53558", + "channelId": 1561661 + }, + { + "zipcode": "53559", + "channelId": 1561660 + }, + { + "zipcode": "53560", + "channelId": 1561661 + }, + { + "zipcode": "53562", + "channelId": 1561661 + }, + { + "zipcode": "53563", + "channelId": 1561284 + }, + { + "zipcode": "53565", + "channelId": 1561661 + }, + { + "zipcode": "53566", + "channelId": 1561661 + }, + { + "zipcode": "53570", + "channelId": 1561661 + }, + { + "zipcode": "53571", + "channelId": 1561661 + }, + { + "zipcode": "53572", + "channelId": 1561661 + }, + { + "zipcode": "53574", + "channelId": 1561661 + }, + { + "zipcode": "53575", + "channelId": 1561661 + }, + { + "zipcode": "53576", + "channelId": 1561661 + }, + { + "zipcode": "53579", + "channelId": 1561660 + }, + { + "zipcode": "53580", + "channelId": 1561661 + }, + { + "zipcode": "53582", + "channelId": 1561661 + }, + { + "zipcode": "53585", + "channelId": 1561631 + }, + { + "zipcode": "53589", + "channelId": 1561661 + }, + { + "zipcode": "53590", + "channelId": 1561661 + }, + { + "zipcode": "53593", + "channelId": 1561661 + }, + { + "zipcode": "53594", + "channelId": 1561284 + }, + { + "zipcode": "53595", + "channelId": 1561661 + }, + { + "zipcode": "53596", + "channelId": 1561661 + }, + { + "zipcode": "53597", + "channelId": 1561661 + }, + { + "zipcode": "53598", + "channelId": 1561661 + }, + { + "zipcode": "53701", + "channelId": 1561661 + }, + { + "zipcode": "53702", + "channelId": 1561661 + }, + { + "zipcode": "53703", + "channelId": 1561661 + }, + { + "zipcode": "53704", + "channelId": 1561661 + }, + { + "zipcode": "53705", + "channelId": 1561661 + }, + { + "zipcode": "53706", + "channelId": 1561661 + }, + { + "zipcode": "53707", + "channelId": 1561661 + }, + { + "zipcode": "53708", + "channelId": 1561661 + }, + { + "zipcode": "53711", + "channelId": 1561661 + }, + { + "zipcode": "53713", + "channelId": 1561661 + }, + { + "zipcode": "53714", + "channelId": 1561661 + }, + { + "zipcode": "53715", + "channelId": 1561661 + }, + { + "zipcode": "53716", + "channelId": 1561661 + }, + { + "zipcode": "53717", + "channelId": 1561661 + }, + { + "zipcode": "53718", + "channelId": 1561661 + }, + { + "zipcode": "53719", + "channelId": 1561661 + }, + { + "zipcode": "53725", + "channelId": 1561661 + }, + { + "zipcode": "53726", + "channelId": 1561661 + }, + { + "zipcode": "53744", + "channelId": 1561661 + }, + { + "zipcode": "53774", + "channelId": 1561661 + }, + { + "zipcode": "53777", + "channelId": 1561661 + }, + { + "zipcode": "53778", + "channelId": 1561661 + }, + { + "zipcode": "53779", + "channelId": 1561661 + }, + { + "zipcode": "53782", + "channelId": 1561661 + }, + { + "zipcode": "53783", + "channelId": 1561661 + }, + { + "zipcode": "53784", + "channelId": 1561661 + }, + { + "zipcode": "53785", + "channelId": 1561661 + }, + { + "zipcode": "53786", + "channelId": 1561661 + }, + { + "zipcode": "53788", + "channelId": 1561661 + }, + { + "zipcode": "53789", + "channelId": 1561661 + }, + { + "zipcode": "53790", + "channelId": 1561661 + }, + { + "zipcode": "53791", + "channelId": 1561661 + }, + { + "zipcode": "53792", + "channelId": 1561661 + }, + { + "zipcode": "53793", + "channelId": 1561661 + }, + { + "zipcode": "53794", + "channelId": 1561661 + }, + { + "zipcode": "53901", + "channelId": 1561660 + }, + { + "zipcode": "53911", + "channelId": 1561661 + }, + { + "zipcode": "53916", + "channelId": 1561649 + }, + { + "zipcode": "53919", + "channelId": 1561649 + }, + { + "zipcode": "53920", + "channelId": 1561660 + }, + { + "zipcode": "53922", + "channelId": 1561657 + }, + { + "zipcode": "53923", + "channelId": 1561660 + }, + { + "zipcode": "53925", + "channelId": 1561660 + }, + { + "zipcode": "53926", + "channelId": 1561660 + }, + { + "zipcode": "53928", + "channelId": 1561660 + }, + { + "zipcode": "53930", + "channelId": 1561660 + }, + { + "zipcode": "53931", + "channelId": 1561649 + }, + { + "zipcode": "53932", + "channelId": 1561660 + }, + { + "zipcode": "53933", + "channelId": 1561649 + }, + { + "zipcode": "53935", + "channelId": 1561660 + }, + { + "zipcode": "53939", + "channelId": 1561649 + }, + { + "zipcode": "53946", + "channelId": 1561649 + }, + { + "zipcode": "53947", + "channelId": 1561649 + }, + { + "zipcode": "53949", + "channelId": 1561660 + }, + { + "zipcode": "53952", + "channelId": 1561660 + }, + { + "zipcode": "53953", + "channelId": 1561660 + }, + { + "zipcode": "53954", + "channelId": 1561660 + }, + { + "zipcode": "53955", + "channelId": 1561660 + }, + { + "zipcode": "53956", + "channelId": 1561660 + }, + { + "zipcode": "53957", + "channelId": 1561649 + }, + { + "zipcode": "53960", + "channelId": 1561660 + }, + { + "zipcode": "53963", + "channelId": 1561657 + }, + { + "zipcode": "53964", + "channelId": 1561660 + }, + { + "zipcode": "53965", + "channelId": 1561660 + }, + { + "zipcode": "53969", + "channelId": 1561661 + }, + { + "zipcode": "54106", + "channelId": 1561657 + }, + { + "zipcode": "54110", + "channelId": 1561599 + }, + { + "zipcode": "54113", + "channelId": 1561657 + }, + { + "zipcode": "54115", + "channelId": 1561657 + }, + { + "zipcode": "54123", + "channelId": 1561657 + }, + { + "zipcode": "54126", + "channelId": 1561599 + }, + { + "zipcode": "54129", + "channelId": 1561657 + }, + { + "zipcode": "54130", + "channelId": 1561657 + }, + { + "zipcode": "54131", + "channelId": 1561657 + }, + { + "zipcode": "54136", + "channelId": 1561657 + }, + { + "zipcode": "54140", + "channelId": 1561657 + }, + { + "zipcode": "54152", + "channelId": 1561657 + }, + { + "zipcode": "54155", + "channelId": 1561657 + }, + { + "zipcode": "54160", + "channelId": 1561599 + }, + { + "zipcode": "54162", + "channelId": 1561657 + }, + { + "zipcode": "54165", + "channelId": 1561657 + }, + { + "zipcode": "54169", + "channelId": 1561657 + }, + { + "zipcode": "54170", + "channelId": 1561657 + }, + { + "zipcode": "54173", + "channelId": 1561657 + }, + { + "zipcode": "54180", + "channelId": 1561657 + }, + { + "zipcode": "54207", + "channelId": 1561599 + }, + { + "zipcode": "54208", + "channelId": 1561599 + }, + { + "zipcode": "54214", + "channelId": 1561599 + }, + { + "zipcode": "54215", + "channelId": 1561599 + }, + { + "zipcode": "54220", + "channelId": 1561599 + }, + { + "zipcode": "54221", + "channelId": 1561599 + }, + { + "zipcode": "54227", + "channelId": 1561599 + }, + { + "zipcode": "54228", + "channelId": 1561599 + }, + { + "zipcode": "54229", + "channelId": 1561599 + }, + { + "zipcode": "54230", + "channelId": 1561599 + }, + { + "zipcode": "54232", + "channelId": 1561599 + }, + { + "zipcode": "54240", + "channelId": 1561599 + }, + { + "zipcode": "54241", + "channelId": 1561599 + }, + { + "zipcode": "54245", + "channelId": 1561599 + }, + { + "zipcode": "54247", + "channelId": 1561599 + }, + { + "zipcode": "54301", + "channelId": 1561657 + }, + { + "zipcode": "54302", + "channelId": 1561599 + }, + { + "zipcode": "54303", + "channelId": 1561657 + }, + { + "zipcode": "54304", + "channelId": 1561657 + }, + { + "zipcode": "54305", + "channelId": 1561657 + }, + { + "zipcode": "54306", + "channelId": 1561657 + }, + { + "zipcode": "54307", + "channelId": 1561657 + }, + { + "zipcode": "54308", + "channelId": 1561657 + }, + { + "zipcode": "54311", + "channelId": 1561599 + }, + { + "zipcode": "54313", + "channelId": 1561657 + }, + { + "zipcode": "54324", + "channelId": 1561657 + }, + { + "zipcode": "54344", + "channelId": 1561657 + }, + { + "zipcode": "54901", + "channelId": 1561657 + }, + { + "zipcode": "54902", + "channelId": 1561657 + }, + { + "zipcode": "54903", + "channelId": 1561657 + }, + { + "zipcode": "54904", + "channelId": 1561657 + }, + { + "zipcode": "54906", + "channelId": 1561657 + }, + { + "zipcode": "54911", + "channelId": 1561657 + }, + { + "zipcode": "54912", + "channelId": 1561657 + }, + { + "zipcode": "54913", + "channelId": 1561657 + }, + { + "zipcode": "54914", + "channelId": 1561657 + }, + { + "zipcode": "54915", + "channelId": 1561657 + }, + { + "zipcode": "54919", + "channelId": 1561657 + }, + { + "zipcode": "54922", + "channelId": 1561657 + }, + { + "zipcode": "54923", + "channelId": 1561615 + }, + { + "zipcode": "54927", + "channelId": 1561657 + }, + { + "zipcode": "54931", + "channelId": 1561657 + }, + { + "zipcode": "54932", + "channelId": 1561657 + }, + { + "zipcode": "54934", + "channelId": 1561649 + }, + { + "zipcode": "54935", + "channelId": 1561657 + }, + { + "zipcode": "54936", + "channelId": 1561657 + }, + { + "zipcode": "54937", + "channelId": 1561657 + }, + { + "zipcode": "54941", + "channelId": 1561649 + }, + { + "zipcode": "54942", + "channelId": 1561657 + }, + { + "zipcode": "54944", + "channelId": 1561657 + }, + { + "zipcode": "54947", + "channelId": 1561657 + }, + { + "zipcode": "54952", + "channelId": 1561657 + }, + { + "zipcode": "54956", + "channelId": 1561657 + }, + { + "zipcode": "54957", + "channelId": 1561657 + }, + { + "zipcode": "54960", + "channelId": 1561649 + }, + { + "zipcode": "54963", + "channelId": 1561657 + }, + { + "zipcode": "54964", + "channelId": 1561657 + }, + { + "zipcode": "54968", + "channelId": 1561649 + }, + { + "zipcode": "54971", + "channelId": 1561649 + }, + { + "zipcode": "54974", + "channelId": 1561657 + }, + { + "zipcode": "54979", + "channelId": 1561657 + }, + { + "zipcode": "54980", + "channelId": 1561649 + }, + { + "zipcode": "54985", + "channelId": 1561657 + }, + { + "zipcode": "54986", + "channelId": 1561657 + }, + { + "zipcode": "60001", + "channelId": 1561631 + }, + { + "zipcode": "60002", + "channelId": 1561624 + }, + { + "zipcode": "60010", + "channelId": 1561631 + }, + { + "zipcode": "60011", + "channelId": 1561631 + }, + { + "zipcode": "60012", + "channelId": 1561631 + }, + { + "zipcode": "60013", + "channelId": 1561631 + }, + { + "zipcode": "60014", + "channelId": 1561631 + }, + { + "zipcode": "60015", + "channelId": 1561639 + }, + { + "zipcode": "60020", + "channelId": 1561631 + }, + { + "zipcode": "60021", + "channelId": 1561631 + }, + { + "zipcode": "60030", + "channelId": 1561631 + }, + { + "zipcode": "60031", + "channelId": 1561639 + }, + { + "zipcode": "60033", + "channelId": 1561631 + }, + { + "zipcode": "60034", + "channelId": 1561631 + }, + { + "zipcode": "60035", + "channelId": 1561639 + }, + { + "zipcode": "60037", + "channelId": 1561639 + }, + { + "zipcode": "60039", + "channelId": 1561631 + }, + { + "zipcode": "60040", + "channelId": 1561639 + }, + { + "zipcode": "60041", + "channelId": 1561631 + }, + { + "zipcode": "60042", + "channelId": 1561631 + }, + { + "zipcode": "60044", + "channelId": 1561639 + }, + { + "zipcode": "60045", + "channelId": 1561639 + }, + { + "zipcode": "60046", + "channelId": 1561631 + }, + { + "zipcode": "60047", + "channelId": 1561631 + }, + { + "zipcode": "60048", + "channelId": 1561639 + }, + { + "zipcode": "60049", + "channelId": 1561631 + }, + { + "zipcode": "60050", + "channelId": 1561631 + }, + { + "zipcode": "60051", + "channelId": 1561631 + }, + { + "zipcode": "60060", + "channelId": 1561639 + }, + { + "zipcode": "60061", + "channelId": 1561639 + }, + { + "zipcode": "60064", + "channelId": 1561639 + }, + { + "zipcode": "60069", + "channelId": 1561639 + }, + { + "zipcode": "60071", + "channelId": 1561631 + }, + { + "zipcode": "60072", + "channelId": 1561631 + }, + { + "zipcode": "60073", + "channelId": 1561631 + }, + { + "zipcode": "60075", + "channelId": 1561639 + }, + { + "zipcode": "60079", + "channelId": 1561639 + }, + { + "zipcode": "60081", + "channelId": 1561631 + }, + { + "zipcode": "60083", + "channelId": 1561639 + }, + { + "zipcode": "60084", + "channelId": 1561631 + }, + { + "zipcode": "60085", + "channelId": 1561639 + }, + { + "zipcode": "60086", + "channelId": 1561631 + }, + { + "zipcode": "60087", + "channelId": 1561639 + }, + { + "zipcode": "60088", + "channelId": 1561639 + }, + { + "zipcode": "60089", + "channelId": 1561639 + }, + { + "zipcode": "60092", + "channelId": 1561631 + }, + { + "zipcode": "60096", + "channelId": 1561639 + }, + { + "zipcode": "60097", + "channelId": 1561631 + }, + { + "zipcode": "60098", + "channelId": 1561631 + }, + { + "zipcode": "60099", + "channelId": 1561639 + }, + { + "zipcode": "60102", + "channelId": 1561631 + }, + { + "zipcode": "60142", + "channelId": 1561631 + }, + { + "zipcode": "60152", + "channelId": 1561631 + }, + { + "zipcode": "60156", + "channelId": 1561631 + }, + { + "zipcode": "60180", + "channelId": 1561631 + } + ] + }, + "termOptions": [ + { + "value": 12, + "desc": "12 months / 1 year" + }, + { + "value": 24, + "desc": "24 months / 2 years" + }, + { + "value": 36, + "desc": "36 months / 3 years" + }, + { + "value": 48, + "desc": "48 months / 4 years" + }, + { + "value": 60, + "desc": "60 months / 5 years" + }, + { + "value": 63, + "desc": "63 months" + }, + { + "value": 72, + "desc": "72 months / 6 years" + }, + { + "value": 75, + "desc": "75 months" + }, + { + "value": 84, + "desc": "84 months / 7 years" + }, + { + "value": 120, + "desc": "120 months / 10 years" + }, + { + "value": 144, + "desc": "144 months / 12 years" + }, + { + "value": 180, + "desc": "180 months / 15 years" + }, + { + "value": 240, + "desc": "240 months / 20 years" + } + ], + "termOptionsNoYear": [ + { + "value": 12, + "desc": "12 months" + }, + { + "value": 24, + "desc": "24 months" + }, + { + "value": 36, + "desc": "36 months" + }, + { + "value": 48, + "desc": "48 months" + }, + { + "value": 60, + "desc": "60 months" + }, + { + "value": 63, + "desc": "63 months" + }, + { + "value": 72, + "desc": "72 months" + }, + { + "value": 75, + "desc": "75 months" + }, + { + "value": 84, + "desc": "84 months" + }, + { + "value": 120, + "desc": "120 months" + }, + { + "value": 144, + "desc": "144 months" + }, + { + "value": 180, + "desc": "180 months" + }, + { + "value": 240, + "desc": "240 months" + } + ], + "abaRouting": "275079714", + "contact": { + "HelpPhone": "262-796-4500" + }, + "links": { + "PrivacyPolicy": "https://www.example.com/", + "MemberSite": "https://www.example.net", + "HomePage": "https://www.landmarkcu.com/" + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/applicant-icon-blue-01.png b/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/applicant-icon-blue-01.png new file mode 100644 index 0000000000000000000000000000000000000000..59d80639e2461e993c9b9f91eef7e67bb70a0eb0 GIT binary patch literal 4618 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 A#Q*>R literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/applicant-icon-gray-01.png b/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/applicant-icon-gray-01.png new file mode 100644 index 0000000000000000000000000000000000000000..d6f3c588bff7c2a7a4957b18d510eb946fb5d33e GIT binary patch literal 2955 zcmV;63v~2}P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf3m{2EK~#8N?VIaU z)H)c4BcR+_LGFSe0)nV0AJqSU#u@LTj#m^xKmp}`7xA1t*#?*Jw%c^O%W)3ROb6(8 z+vJz`mNcCCN25-Oq%8P(N(84O#o$z=82l$<(!s%j+TY(dho!8nOr1S@R#j9~nBPy5 zl8dyvyQ@}LSJnFZy4u>>Iwp>b6|1tcQq|YjtA>UKb^iSMk_uN6kq~BneqR0f@k9Om z`BT|Po;h(wu`#%!aB$Iug#}}wSSMV#a6w(Ua>WcrnxrZc4S)CUof&ax zX=zbCJw582cBNvnw6v_Ir>BftGbk4?UR0MZT?#%eoYX}6`t_@^9w4|54h|Mw9GCCk zzpKf~Ns}#cy&E@fq|$~|M0)e)jfoXyW#wvMpx>B%9B@ahj`%`=rgOi3{W5`>gAP_z zSF0Kw+%r0}mB)0W47jP8|<>dwYXV{7Ebl9en!qsS%3o(%;`7 zd~zHfLCjcKSTw(vmzReM6NYUjo#vUC&CSgnR?a@cgh7)zXb1Nl)SYk8?ij|iXU|Mfr}P*a8Vb(F z$6F*WfAZvs86`{@QSJoQ;RKNH1QzFvVe% zpJP2_J1Xd6iIWRmU@+k3&6_56Nj!S=$haP%fe;ZFuL;nnPoIoHjk+id6NpUr?c2AK z2?RU`YsHSqi}6xBsg=F*RNljdgr7BvRI+8dTKWp zN(fwysf;at^ASs=&!0aVUnUNbdR-%ueEP=yU zD8%M@%;Q{Gtzhy@$e>+eMD~z+hmnw$NLiBbt%c|u*JIwk)(2Q~e*&QxNUCkt)m=zN zw1prZMyN=3mfqNOe6(pKb(60^WD-^i3ZX>{)!f_?Ir0)%6HCc1lmqmGxEDsKNSGkB zwj3|tu`nL$BNClzZfOafGWfun75DRXI|iNs&V}>19i@W%nI%#rT#e$(1=W3G5uG7W z_X;IMQV0P))`S=wlEqxm*2%qCgOU(?N{3LEpysl>yJOtY7gDU7!3u%ZMqig>U?m~W zXENp|Aw)tHiOfXuC5dDCpi{okc4{UU8!shm5=F8Bk?fWW{_h(s zfrjP2mQDjkJgM+fs<$FZ_Y_46s}~T-Cs0}tiCC55yTv!w-Y;?lT$AJ!g$urxI3xzJRBS~}c1W8=$J-ayB&fO#EH-g{aU_1TlhPtAz?%o|;3yR-@!RrZKavvW4K;kocS zIY}8{u|ln_u9~G^*3YA$@wn*AmoN2qv(}gtJGgA_aM{?{G-ZGkYQ!~eDsEo#G1GQ8 zLNKB<*muEAf=8205S>k<%*Tuf`o4R6yJ};7!?*$Nl_cG}@En8~9UTqM{Neun`)0|6 zJxwm+uwag6w1rtQ9g)N;HS$z@*)H#<9{lfb+Y+qh{23@X>LzV5<4E1rGt z-o4<=Q9?zca}ORo(4$wc{q$yV_7ChXVKl=U$8C8qTV3MrGUIvy~-0>lk*xt_;ZA743R`)V9J%k@zB`D{W(e!NIG>n%surE>U|Ziv_RZ5hNFcBV;rR zFErSuNLaDP5Jq&g({$;;i3q%mbiu^gM3L)u{O-HmE-lDTOgXG|8>`mG@UR?fcted9 zu|YM8ydn{Ju_k=i9VOrmD6z|?V;(AHu~I<{v~3}=3=a*7$z^Klb1W-@ydnYruL0~J zlNcEpF#!}SiVA?G0Bb@N2DDdOG_li#+H7tvyJaz-6+tYKs8T89c#A?kBQZ9Xtv^oE ziYyeI3|6Q10tt5IX=fC-<4DBbeamGJA3ikxep{#9YGJv7(Ziym3d~Y~n-MQ1*d|CK zVlWBxafoF}K*#My@t3-gl)!vXS|A7$h;)xYd>&UIh%FLOm6GiUc&rlqO|Z#yV&bET zSJEm#6kZ|$b1>>B7UY4nT(&4Cw1vIdBo`JA`G=fBGj1v(0gYnx?6w^i z%X(7$XW@M8=tMH12g*L51VbiHgVaQ_P>b+C8%$Bk^ayt9xe@cDVp$fMiw&cscr1pQ z0sF&!GguU!6l(s=y?ANBfKZCi1*RgbJ|I%s6ip-x z6)sB>bOv$AG^{>m7wziJ?@>E)5t)6Oo10r^A2z|5X$R1BYBnaR#GhhObdfBG#K;p^ z`Q5JhY7}Er{du-;ae*GLdpYAYO73PgsaYG$zNca0o)}g{5IB zxKtsjJSit4(%**n-0lC!sYo$66)6U%BIQM?e*vx@n+`Jh{B8gM002ovPDHLkV1lj* Bc8mZ3 literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/collateral-icon-blue.png b/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/collateral-icon-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..84e9a1736f86572c05f40c23571bc506fe4ff0db GIT binary patch literal 5821 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 literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/finish-icon-gray.png b/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/finish-icon-gray.png new file mode 100644 index 0000000000000000000000000000000000000000..4f20e3935b801689e195c1a411d45c0c216f9d55 GIT binary patch literal 3394 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 YA2D|y^W`6#$N&HU07*qoM6N<$g3y>L)&Kwi literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/review-icon-blue.png b/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/review-icon-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..d0afbfb31bf687160f0a68969d0021c1a65ec64f GIT binary patch literal 5158 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 UO<#L)V*mgE07*qoM6N<$g3J#MNB{r; literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/review-icon-gray.png b/LCU.WebApps.External.OnlineLoanApp/src/assets/waypoint/review-icon-gray.png new file mode 100644 index 0000000000000000000000000000000000000000..f84cd6291209f29c3313c7380c234cd2551acd89 GIT binary patch literal 3316 zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf42elZK~#8N<(&6h z6H63^htL#3X$m4(zygT9{r^7-Vgtc~f(nWSMQJMS&U>d3kVXBCoogK5jzHZjm*37S8zs&F7zs;XNf6VswwjATLuCC71)YO>z`g&=N zjg98;;lt9Jo123>RS#7m>F3X%=Iht5W_fv8NJ)!BhYp$c_IA_J(NPuBDn=68vbeY? z6du{N!S{jsF}vTfCB-nwlY z8yhApGUp#Ze%zcqc~Ug45-2-K-@ku1^Yim&b#+xb&FyvM+iW}9(h{6mBJBNspqWcc zX4yUqQ{_3J#oqhexpOiIuf2PAgsXuqYtr-Y-c_m*RQie+5b;=cbl_k&t?zq6Yzg;W@pXUFJENPYHMrF;NYN`VyP&Z zB(w*T)DaI24N1683LcMR{_{JV2JG(c*p6jPrnb&BT8Cb$Gv3%(H`CM4#b=@Ir%s(R zXU?1njwVB5k~sa*qepuWKWiOjk2SAif$#eE?VI=#{1=f7q7{t3iX@-5)mgGyY^XnW z?3ih5Ym-1wEau+MnGXvefUC9tLgU#to6I0WW; zZG{jfj6tCh_PeZUGdTPm2lBLM&z{M1Ise9u8#x0LhoVU$JAU%yiTq7o!-L}R5w6)fz>&+BgClN0 z=!+LGB=LisVl<`{AP)@!$ipsPycqkO@$vCJ6TfxqR#6%iOA^dKcK6B=q~hIcU`8=B5Oin>TL;NBoE-Nu4y)hwIm_1>fd_hfYpT z%DJjN)dpQb4xBq--v=V-`1nIHS)BRE$VhO+je@#Hn8(ym$?Mih`>?X|(>%0fqQlR znvD64KYS>f?E=B)lJ#Nq50Xend~o)P3tzo@C7mV#^Gz6;ir}Cbz5UW2ACNs`WfFH` zP0$6*^2CWQId^(`I`}ppPLk+2oravAo}S>_T)_PF^puQ9IS6847=3lXw6qYSY_Rd4^IOI0+LAvDV#+?2?T$k9ZdrW z?>ydV2KwDqf#=VkOXZa0G#VLmp3F%kfC%m*h=SU$;G8sopr)q7=FXivPJ#fY&T)Jm zBb^4A)ThPIVks_AQbAv^B9tVI9FiC{I?s&R1x$c*(uR3DOvgt?;wPJE(6#H=gsBRB-dT2Xpn*P45}82y&hBJ;h;~8-oAZX(9F2D zn8pYlv(7OL$e*2^mHQLn$ZuX)v%o)w(bm=~*SqfPYAzr@#}=(pxIn<-IAvgkkVG1< zAs8m3*xuffvPU6;aKzk$&q9rJs5pkPXa(Zld*`9*_MV?Ve-6%2WQu-| z)YOzH1r>$v?(VE6VQQegy+b~uU_@QMNC~=i>!yV9gFp~K6HcE#E%hY=0dj_cob>0* zm#C&@fi?jgILL4VvP>>yy()@L%Q$(1ivU!bbfK;;`2s*SKxat~qa*=HT3X5~M-=Ka zHK)oDwYUkM-9^NT^;f`05+-(#0a3{50#@Lfh8${a6xYIam}N#Ih_6PBQ}=~^$aVRt zg9M-K3_9|Y8@j+!7RRxoJ(7Io5H>2fwCHaD1o}u)rGcM}X1Knkp^f$Q^R$Bu-&P>y z19Qx%P1Z+p7X>-e-wFnw)ngaP8G-mD_58I5+_zK$kmQ;x0`;{V jySKj|JKOHa zr!EF1`j?x(*zcr;b?{l9*nanaL!lH#-4GgOC)`;L+O`g=5+H=KXh~T(f5N)|sIPaE zgu@0h_nVoS*+uvZ7cMxnXV$KEt>02I!yqzJUM(~l)QHeluIGFf7g)*(>SR8=Acqn3N(w?&MrFOfGpq zc)C_&)AHS?PoK&^*Mow47>pA271T9qa&hJcsPIv*LZDVQGB|46fXvrr(sAIvg|-a( z%uNUF_U+rE=}}Nd=D2LnVYCqrA-OOLw%e27YG73tV zsNUZK2At+w6jdHH3g?id<;o~X4bX(N+H_DrKX5ZHQ{@;HNh~ObB+9lpHvE!rpAM|+ zJnz2_tfH^4FZeGPXc5ODP)Xc#6lfFd`IcHpsvs$;HZdyf=olLvGc&Jef^YMo_fFc9 z4jkv>gQE$pLKkTXIEuYpCFBKcLFNP^5uZ`8K*`FchSS)L+qThA#!cA)vZH$q<`?E| zcDo?E<&uIfD`%%k z65qu8X>;rlNfNAz+1bD6 z>`e;Qnq15uB=C+L5zqx9Eh!CdgO?h8aFPVDUqT^-QGQJW-gBeU=OcSRzy^5TuTI`p z==TL;U%=6KG!9w2zEJPq>mM%`Lu67Q7Y;YcTig2P6%m>M6o*);aA=G^ zKqDrtW)zEXr*$k`MJ_hZLu9E9Ei4vHy&#A|b5&wpC~D}45c-1envHm9M=@X)-O<@8 zLSeGo+dIw#HJgRIryOyO>u}C-K#O^~QMS_9!6P!e2F!(6a|?q^992~T8Hr+|8{1nD zPBxRvuI%c9=TA;dm>NqC#E=pcOAtsz62J$bNvJrZ({Fu2cc`pz(7^6~*3Zx$QWkX` zNsyQ%1uv{afCe#xD8_I3kcuRL$uY|(dsU%&dwadh&Xodg0MiQppbH7HuNapEsYz0h9x#IB zMoJb!QgZPvC6@z~VyIYP`brMTDtQw=s2nI8NeVQI6QZT~$<4>5RB4eF9KT`%-j5JzCK1KE$F5VG6$*q3d z9E+C_*{eV~nA#7frQjNtyeg@&6(cDOw2Pt^geA$2eSlOZ-)JV&oJylAB!z)ip=D?U yCW*N~9NNLBnhw(q5 literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/browserslist b/LCU.WebApps.External.OnlineLoanApp/src/browserslist new file mode 100644 index 0000000..8e09ab4 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/browserslist @@ -0,0 +1,9 @@ +# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# For IE 9-11 support, please uncomment the last line of the file and adjust as needed +> 0.5% +last 2 versions +Firefox ESR +not dead +# IE 9-11 \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/src/environments/enivronment-common.ts b/LCU.WebApps.External.OnlineLoanApp/src/environments/enivronment-common.ts new file mode 100644 index 0000000..6917a60 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/environments/enivronment-common.ts @@ -0,0 +1,2 @@ +export const ENVIRONMENT_COMMON = { +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.dev.ts b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.dev.ts new file mode 100644 index 0000000..2d79abb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.dev.ts @@ -0,0 +1,33 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +// There is an runner in package.json that will update this version file. +import { VERSION, BUILD_STAMP } from './version'; +import { ENVIRONMENT_COMMON } from './enivronment-common'; + +export const environment = { + name: 'dev', + version: VERSION, + buildTs: BUILD_STAMP, + production: false, + testGuard: true, + memberGuard: false, + coreResolver: true, + useLocalSettings: false, + enableTimeout: true, + displayLoanId: true, + urlBase: 'https://qa-online-loan-app-api.apps.azure.lcu-internal.com/api', + authUrlBase: 'https://qa-vendor-api.apps.azure.lcu-internal.com/api', + obsUri: 'https://www.landmarkcuonline.com/tob/beta/usp-core/app/login/consumer', + msAuthRedirectUri: 'https://qa-online-loan-app.apps.azure.lcu-internal.com/loanapp/admin', + baseHref: '/loanapp' +}; + +/* + * In development mode, to ignore zone related error stack frames such as + * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can + * import the following file, but please comment it out in production mode + * because it will have performance impact when throw error + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.e2e.ts b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.e2e.ts new file mode 100644 index 0000000..5e88b8a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.e2e.ts @@ -0,0 +1,16 @@ +import { VERSION } from './version'; +import { ENVIRONMENT_COMMON } from './enivronment-common'; + +export const environment = { + name: 'e2e', + version: VERSION, + production: false, + testGuard: false, + memberGuard: false, + coreResolver: false, + useLocalSettings: true, + enableTimeout: false, + displayLoanId: true, + urlBase: 'https://qa-online-loan-app-api.apps.azure.lcu-internal.com/api', + baseHref: '/loanapp' +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.local.ts b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.local.ts new file mode 100644 index 0000000..0c9a644 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.local.ts @@ -0,0 +1,19 @@ +import { VERSION } from './version'; +import { ENVIRONMENT_COMMON } from './enivronment-common'; + +export const environment = { + name: 'local', + version: VERSION, + production: false, + testGuard: false, + memberGuard: false, + coreResolver: true, + useLocalSettings: true, + enableTimeout: false, + displayLoanId: true, + urlBase: 'https://localhost:44384/api', + authUrlBase: 'https://qa-vendor-api.apps.azure.lcu-internal.com/api', + obsUri: 'https://www.landmarkcuonline.com/tob/beta/usp-core/app/login/consumer', + msAuthRedirectUri: 'https://localhost:8080/loanapp/admin', + baseHref: '/loanapp' +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.prod.ts b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.prod.ts new file mode 100644 index 0000000..98df96a --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.prod.ts @@ -0,0 +1,19 @@ +import { VERSION } from './version'; +import { ENVIRONMENT_COMMON } from './enivronment-common'; + +export const environment = { + name: 'prod', + version: VERSION, + production: true, + testGuard: false, + memberGuard: false, + coreResolver: true, + useLocalSettings: false, + enableTimeout: true, + displayLoanId: false, + urlBase: 'https://prd-online-loan-app-api.apps.azure.lcu-internal.com/api', + authUrlBase: 'https://prd-vendor-api.apps.azure.lcu-internal.com/api', + obsUri: 'https://www.landmarkcuonline.com/tob/live/usp-core/app/login/consumer', + msAuthRedirectUri: 'https://prd-online-loan-app.apps.azure.lcu-internal.com/loanapp/admin', + baseHref: '/loanapp' +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.qa.ts b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.qa.ts new file mode 100644 index 0000000..da7b9cb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.qa.ts @@ -0,0 +1,19 @@ +import { VERSION } from './version'; +import { ENVIRONMENT_COMMON } from './enivronment-common'; + +export const environment = { + name: 'qa', + version: VERSION, + production: true, + testGuard: true, + memberGuard: false, + coreResolver: false, + useLocalSettings: false, + enableTimeout: true, + displayLoanId: true, + urlBase: 'https://qa-online-loan-app-api.apps.azure.lcu-internal.com/api', + authUrlBase: 'https://qa-vendor-api.apps.azure.lcu-internal.com/api', + obsUri: 'https://www.landmarkcuonline.com/tob/beta/usp-core/app/login/consumer', + msAuthRedirectUri: 'https://qa-online-loan-app.apps.azure.lcu-internal.com/loanapp/admin', + baseHref: '/loanapp' +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.ts b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.ts new file mode 100644 index 0000000..4cc3ebd --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.ts @@ -0,0 +1,90 @@ +import { environment as devEnvironment } from './environment.dev'; +import { environment as localEnvironment } from './environment.local'; +import { environment as prodEnvironment } from './environment.prod'; +import { environment as qaEnvironment } from './environment.qa'; +import { environment as uatEnvironment } from './environment.uat'; + +export type EnvironmentConfiguration = + | typeof devEnvironment + | typeof localEnvironment + | typeof prodEnvironment + | typeof qaEnvironment + | typeof uatEnvironment; + +function normalizeHost(hostname?: string): string { + return (hostname || '').toLowerCase(); +} + +function normalizePath(pathname?: string): string { + return (pathname || '').toLowerCase(); +} + +function matchHostname( + hostname: string, + patterns: ReadonlyArray +): boolean { + return patterns.some((pattern) => hostname.indexOf(pattern) !== -1); +} + +function shouldUseAppsLoanApi(hostname: string): boolean { + return hostname.endsWith('apps.landmarkcu.com'); +} + +export function getEnvironmentFromWindowLocation(): EnvironmentConfiguration { + if (typeof window === 'undefined') { + return prodEnvironment; + } + + const location = window.location; + const hostInput = location ? location.hostname : undefined; + const hostname = normalizeHost(hostInput); + + if (!hostname) { + return prodEnvironment; + } + + if (hostname === 'localhost' || hostname === '127.0.0.1') { + return localEnvironment; + } + + if (hostname.startsWith('qa-')) { + return qaEnvironment; + } + + if (hostname.startsWith('uat-')) { + return uatEnvironment; + } + + if (hostname.startsWith('prd-')) { + return prodEnvironment; + } + + return prodEnvironment; +} + +export const environment: EnvironmentConfiguration = prodEnvironment; + +function applyRuntimeEnvironment(): void { + const runtimeEnvironment = getEnvironmentFromWindowLocation(); + + if (runtimeEnvironment !== environment) { + for (const key in runtimeEnvironment) { + if (!Object.prototype.hasOwnProperty.call(runtimeEnvironment, key)) { + continue; + } + + (environment as any)[key] = (runtimeEnvironment as any)[key]; + } + } + + if (typeof window !== 'undefined') { + const location = window.location; + const hostname = normalizeHost(location ? location.hostname : undefined); + + if (shouldUseAppsLoanApi(hostname)) { + environment.urlBase = `https://${hostname}/loanapi/api`; + } + } +} + +applyRuntimeEnvironment(); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.uat.ts b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.uat.ts new file mode 100644 index 0000000..9faaa1e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/environments/environment.uat.ts @@ -0,0 +1,19 @@ +import { VERSION } from './version'; +import { ENVIRONMENT_COMMON } from './enivronment-common'; + +export const environment = { + name: 'uat', + version: VERSION, + production: true, + testGuard: true, + memberGuard: false, + coreResolver: false, + useLocalSettings: false, + enableTimeout: true, + displayLoanId: true, + urlBase: 'https://uat-online-loan-app-api.apps.azure.lcu-internal.com/api', + authUrlBase: 'https://uat-vendor-api.apps.azure.lcu-internal.com/api', + obsUri: 'https://www.landmarkcuonline.com/tob/beta/usp-core/app/login/consumer', + msAuthRedirectUri: 'https://uat-online-loan-app.apps.azure.lcu-internal.com/loanapp/admin', + baseHref: '/loanapp' +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/favicon.ico b/LCU.WebApps.External.OnlineLoanApp/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2f13c104e6bac7c8f84be233777ba0eb6570aad0 GIT binary patch literal 2819 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 literal 0 HcmV?d00001 diff --git a/LCU.WebApps.External.OnlineLoanApp/src/index.html b/LCU.WebApps.External.OnlineLoanApp/src/index.html new file mode 100644 index 0000000..7f13f12 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + + Landmark Credit Union Application For Credit + + + + + + + + + + + + diff --git a/LCU.WebApps.External.OnlineLoanApp/src/karma.conf.js b/LCU.WebApps.External.OnlineLoanApp/src/karma.conf.js new file mode 100644 index 0000000..9edd22d --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/karma.conf.js @@ -0,0 +1,75 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '../', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-ie-launcher'), + require('karma-firefox-launcher'), + require('karma-edge-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('karma-junit-reporter'), + require('karma-spec-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false, // leave Jasmine Spec Runner output visible in browser + captureConsole: true, + mocha: { bail: false } + }, + browserConsoleLogOptions: { + level: 'debug', + format: '%b %T: %m', + terminal: true + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, '../coverage'), + reports: ['html', 'lcovonly'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'spec', 'junit', 'kjhtml'], + files: [ + { pattern: 'src/assets/**/*', watched: false, included: false, served: true, nocache: false } + ], + proxies: { + '/assets/': '/base/src/assets/' + }, + specReporter: { + maxLogLines: 5, + suppressErrorSummary: false, + suppressFailed: false, + suppressPassed: true, + showSpecTiming: true + }, + junitReporter: { + outputDir: '../test-results', // results will be saved as $outputDir/$browserName.xml + outputFile: 'test-results.xml', // if included, results will be saved as $outputDir/$browserName/$outputFile + suite: '', // suite will become the package name attribute in xml testsuite element + useBrowserName: true, // add browser name to report and classes names + nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element + classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element + properties: {}, // key value pair of properties to add to the section of the report + xmlVersion: 1 // use '1' if reporting to be per SonarQube 6.2 XML format + }, + port: 9876, + colors: true, + logLevel: config.LOG_DEBUG, + autoWatch: true, + customLaunchers: { + ChromeDebug: { + base: 'ChromeHeadless', + flags: [ + '--remote-debugging-port=9222', + '--disable-web-security', + ] + } + }, + browsers: ['ChromeHeadless'], + singleRun: false + }); +}; diff --git a/LCU.WebApps.External.OnlineLoanApp/src/main.ts b/LCU.WebApps.External.OnlineLoanApp/src/main.ts new file mode 100644 index 0000000..91ec6da --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.log(err)); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/polyfills.ts b/LCU.WebApps.External.OnlineLoanApp/src/polyfills.ts new file mode 100644 index 0000000..00250f1 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/polyfills.ts @@ -0,0 +1,80 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/weak-map'; +import 'core-js/es6/set'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following for the Reflect API. */ +import 'core-js/es6/reflect'; + + +/** Evergreen browsers require these. **/ +// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. +import 'core-js/es7/reflect'; + + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + */ + + // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + + /* + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + */ +// (window as any).__Zone_enable_cross_context_check = true; + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/LCU.WebApps.External.OnlineLoanApp/src/styles.scss b/LCU.WebApps.External.OnlineLoanApp/src/styles.scss new file mode 100644 index 0000000..cdf8df6 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/styles.scss @@ -0,0 +1,455 @@ +/* You can add global styles to this file, and also import other style files */ +@import 'variables'; +@import "~node_modules/bootstrap/scss/bootstrap"; +@import '~@angular/material/prebuilt-themes/indigo-pink.css'; + + +/* Form specific styles to match Landmark requirements */ +$base: #353432; +$primary: #005674; +$primary-dark: #053251; +$secondary: #0D3151; +$success: #2c5e29; +$success-dark: #004B00; /* Success 20% Darker */ +$warning: #f7941d; +$error: #f63440; +$light-gray: #d5d3d3; +$disabled: #949699; +$white: #ffffff; +$background: #f8f9fa; +$width-to-mobile: 767px; + +.landmark-hero { + background-color: $primary; + padding: 0.5em 3em; +} + +.landmark-hero-emoji { + font-size: 4.5em; + color: $white; + text-align: center; +} + +.landmark-hero-status { + font-size: 2.25em; + color: $white; + text-align: center; + margin: 0; +} + +.landmark-hero-copy { + font-size: 1.15em; + color: $white; + text-align: center; +} + +.muted-background { + background-color: $background !important; +} +/*** Errors display ***/ +.form-control.ng-invalid.ng-touched .form-control.ng-invalid.ng-dirty { + border-color: $error; +} +.invalid-feedback { + display: block; +} + +/*** Custom buttons***/ +.btn-custom , .btn-custom:visited ,.btn-custom:hover{ + padding-left: 30px !important; + padding-right: 30px !important; +} + +.btn-custom:active, .btn-custom.active { + color: $white; + background-color: $primary !important; +} + +.btn-success { + background-color: $success; + font-size: 16px; + font-family: Arial, sans-serif; +} +.btn-success:hover{ + background-color: $success-dark; +} + +.btn-continue { + margin: 0 0 1em 1em; +} + +.btn-previous { + margin: 0 0 1em 0; + text-decoration: underline; +} + +.btn-disabled { + background-color: #999999; + color: #ffffff; + margin: 0 0 1em 1em; +} + +@media(min-width: 1200px) { + .btn-vertical-divider { + border-right: 1px solid #EAEAEA; + margin-right: 48px; + margin-top: 24px; + } +} + +@media(min-width: 992px) { + .btn-vertical-divider { + border-right: 1px solid #EAEAEA; + margin-right: 48px; + margin-top: 24px; + } +} + +/*** Watermarks ***/ +.watermark-primary {} +.watermark-secondary +{ + max-width: 200px; + position:absolute; + right:10%; + opacity:1; + z-index: -1; +} +//Resize +@media screen and (max-width: $width-to-mobile) { + .watermark-primary { + display:none; + } + .watermark-secondary { + max-width: 75px; + position: absolute; + top:4%; + left: 0; + right: 0; + margin: auto; + } +} + +/*** Color of icons before text***/ +.input-group-prepend .input-group-text { + background-color: $primary; + border-color: $primary; + color: #FFFFFF; +} + +.limited-width-col{ + max-width: 400px; +} + +// checkbox restyling came from a stack overflow question, seems to work really well +// note that it has been customized to look like graydient's design +// https://stackoverflow.com/questions/43520218/how-to-create-a-tick-mark-in-a-custom-checkbox +[type="checkbox"]:not(:checked), +[type="checkbox"]:checked { + position: absolute; + left: -9999px; +} + +[type="checkbox"]:not(:checked)+label, +[type="checkbox"]:checked+label { + position: relative; + padding-left: 25px; + cursor: pointer; +} + +[type="checkbox"]:not(:checked)+label:before, +[type="checkbox"]:checked+label:before { + content: ''; + position: absolute; + left: 0; + top: 4px; + width: 15px; + height: 15px; + background: white; + border: solid gray 1px; + outline: none; +} + +[type="checkbox"]:checked:hover +label:before, +[type="checkbox"]:not(:checked):hover +label:before, +[type="checkbox"]:focus + label::before { + background: $light-gray; + border: solid $primary 1px; +} + +[type="checkbox"]:not(:checked)+label:after, +[type="checkbox"]:checked+label:after { + content: url('/assets/checkbox/check.png'); + position: absolute; + top: -10px; + left: -5px; + font-size: 34px; + line-height: 0.8; + color: green; + transition: all .2s; +} + +[type="checkbox"]:not(:checked)+label:after { + opacity: 0; + transform: scale(0); +} + +[type="checkbox"]:checked+label:after { + opacity: 1; + transform: scale(1); +} + +.checkbox-label { + left: -25px; + top: 40px; +} + +.padding { + padding-left: 1rem; + padding-right: 1rem; +} + +.icon-input-row { + display: flex; + flex-direction: row; + align-items: baseline; +} + +.input-auto { + width: auto; +} + +.input-lmcu-fixed-width { + width: 20em; +} + +.summary-card > .col-4 { + padding-left: 2em; +} +.submit-row { + margin: 2em 0 2em 0; +} + +.disclosure-text { + font-family: Arial, Helvetica, sans-serif; + font-size: 0.97em; + line-height: 1.15em; + } + +/*** Placeholder ***/ +::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ + color: $light-gray !important; + font-family: Arial, sans-serif; + font-style: italic !important; + font-size: 16px !important; + opacity: 1; /* Firefox */ +} + +:-ms-input-placeholder { /* Internet Explorer 10-11 */ + color: $light-gray !important; + font-style: italic !important; + font-size: 16px !important; +} + +::-ms-input-placeholder { /* Microsoft Edge */ + color: $light-gray !important; + font-style: italic !important; + font-size: 1rem !important; +} + +/*** Headers ***/ +h1, h2, h3, h4, h5, h6 { + font-family: 'Open Sans', sans-serif !important; +} + +h1, h2 { + font-size: 44px; + font-weight: 400; + color: $primary; +} + +h3 { + font-size: 24px; + font-weight: 700; + color: $base; +} + +h4 { + font-size: 20px; + font-weight: 400; + color: $base; +} +/*** Hint / helper text ***/ +.hint-field .control-label{ + margin-bottom: 0; +} + +.hint { + display: inline-block; + margin-bottom: 0.5rem; + font-size: 14px; + color: $light-gray; + font-style: italic; +} +/*** Accordion ***/ +.accordion .card{ + border-radius: .25rem; + border: 2px solid $secondary; +} + +.accordion .card:first-of-type{ + border-bottom: 2px solid $secondary; + border-radius: 0.25rem; +} + +.accordion .card .card-header { + position: relative; + background-color: $secondary; + color: $white; + font-size: 1.2rem; + letter-spacing: 1.5px; + border-radius: 0; +} +.accordion .card .card-header a { + color: $white; +} + +.accordion .card .card-header a:hover { + color: $light-gray; +} + +.accordion .card .card-header p { + margin-bottom: 0; +} + +.accordion .card .card-header .card-icon { + position: absolute; + top: 50%; + right: 20px; + transform: translateY(-50%); + font-size: 1.8rem; +} + +html * { + font-family: Arial, sans-serif; +} + +.legal-text { + color: #000000; + border-color: $warning; + border-width: 2px; + font-size: 0.85em; +} + +/*** Modal ***/ +.modal-title { + color: $white !important; +} +.close { + color: $white !important; +} + +.modal-header { + background-color: $primary-dark !important; +} + +.title-hr { + margin: 0 0 0.67em 0; + color: #DFDFDF; + height: 4px; + border: none; + background-color: #DFDFDF; +} + +.optional-text { + color: $light-gray; + font-size: 14px; + font-style: italic; +} + +.form-group { + margin-bottom: 1rem; +} + +/*** Select **/ +.custom-drop-down select { + display: inline-block; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + cursor: pointer; + outline: none; + z-index: 0 !important; + } + +.custom-drop-down .input-group :before { + z-index: 1; + content: ''; + right: 0; + top: 0; + width: 35px; + height: 38px; + background: $primary; + position: absolute; + pointer-events: none; + font-size: 26px; + font-family: monospace !important; + font-style: normal; + font-weight: normal; + border: 4px solid $primary; + border-top-right-radius: .25rem; + border-bottom-right-radius: .25rem; + padding: 2px 2px 2px 2px; +} +.custom-drop-down { position: relative; } +.custom-drop-down:after { + z-index: 1; + content: "▾"; + color: white; + font-size: 26px; + font-family: monospace !important; + font-style: normal; + font-weight: normal; + right: 25px; + top: -3px; + bottom: 0; + background: transparent; + position: absolute; + pointer-events: none; +} + +@-moz-document url-prefix() { + // alignment for firefox + .custom-drop-down:after { + right: 22px; + top: 1px; + } +} +@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { + // alignment for IE + .custom-drop-down:after { + right: 22px; + top: -2px; + } + // fix input box height + .form-control { + padding: 0.55rem 0.75rem !important; + } +} + +// fix height/width for input icons +span.input-group-text { + height: 38px !important; + width: 40px !important; +} +div.input-group-text { + height: 38px !important; + width: 40px !important; +} +// remove internet explorer / edge +input::-ms-clear { display: none; } + +.custom-checkbox-mt { + position: relative; + top: 2px; +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/test.ts b/LCU.WebApps.External.OnlineLoanApp/src/test.ts new file mode 100644 index 0000000..daf1d5f --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/test.ts @@ -0,0 +1,35 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: any; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const contextComponents = require.context('./', true, /\.component\.spec\.ts$/); +const contextServices = require.context('./', true, /\.service\.spec\.ts$/); +const contextPages = require.context('./', true, /-page\.spec\.ts$/); +const translatorClasses = require.context('./', true, /-translator\.spec\.ts$/); +const calculatorClasses = require.context('./', true, /-calculator\.spec\.ts$/); +const utilClasses = require.context('./', true, /-util\.spec\.ts$/); +const optionClasses = require.context('./', true, /-options\.spec\.ts$/); +const routerGuards = require.context('./', true, /.guard\.spec\.ts$/); + +// And load the modules. +contextComponents.keys().map(contextComponents); +contextServices.keys().map(contextServices); +contextPages.keys().map(contextPages); +translatorClasses.keys().map(translatorClasses); +calculatorClasses.keys().map(calculatorClasses); +utilClasses.keys().map(utilClasses); +optionClasses.keys().map(optionClasses); +routerGuards.keys().map(routerGuards); diff --git a/LCU.WebApps.External.OnlineLoanApp/src/tsconfig.app.json b/LCU.WebApps.External.OnlineLoanApp/src/tsconfig.app.json new file mode 100644 index 0000000..722c370 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "module": "es2015", + "types": [] + }, + "exclude": [ + "src/test.ts", + "**/*.spec.ts" + ] +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/tsconfig.spec.json b/LCU.WebApps.External.OnlineLoanApp/src/tsconfig.spec.json new file mode 100644 index 0000000..8f7cede --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/tsconfig.spec.json @@ -0,0 +1,19 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/spec", + "module": "commonjs", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts", + "polyfills.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/tslint.json b/LCU.WebApps.External.OnlineLoanApp/src/tslint.json new file mode 100644 index 0000000..ddaef76 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/tslint.json @@ -0,0 +1,17 @@ +{ + "extends": "../tslint.json", + "rules": { + "directive-selector": [ + true, + "attribute", + "lw", + "camelCase" + ], + "component-selector": [ + true, + "element", + "lw", + "kebab-case" + ] + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/src/web.config b/LCU.WebApps.External.OnlineLoanApp/src/web.config new file mode 100644 index 0000000..e61d741 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/src/web.config @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LCU.WebApps.External.OnlineLoanApp/tsconfig.json b/LCU.WebApps.External.OnlineLoanApp/tsconfig.json new file mode 100644 index 0000000..ef44e28 --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es5", + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2017", + "dom" + ] + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/tslint.json b/LCU.WebApps.External.OnlineLoanApp/tslint.json new file mode 100644 index 0000000..3ea984c --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/tslint.json @@ -0,0 +1,130 @@ +{ + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "arrow-return-shorthand": true, + "callable-types": true, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "deprecation": { + "severity": "warn" + }, + "eofline": true, + "forin": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": [ + true, + "spaces" + ], + "interface-over-type-literal": true, + "label-position": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-super": true, + "no-empty": false, + "no-empty-interface": true, + "no-eval": true, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-misused-new": true, + "no-non-null-assertion": true, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-string-throw": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unnecessary-initializer": true, + "no-unused-expression": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "prefer-const": true, + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + true, + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "unified-signatures": true, + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ], + "no-output-on-prefix": true, + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": true, + "directive-class-suffix": true + } +} diff --git a/LCU.WebApps.External.OnlineLoanApp/version.js b/LCU.WebApps.External.OnlineLoanApp/version.js new file mode 100644 index 0000000..97864ea --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/version.js @@ -0,0 +1,22 @@ +const { gitDescribeSync } = require('git-describe'); +const { version } = require('./package.json'); +const { resolve, relative } = require('path'); +const { writeFileSync } = require('fs-extra'); + +const gitInfo = gitDescribeSync({ + dirtyMark: false, + dirtySemver: false +}); + +gitInfo.version = version; + +const file = resolve(__dirname, '.', 'src', 'environments', 'version.ts'); +writeFileSync(file, + `// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN! +/* tslint:disable */ +export const VERSION = ${JSON.stringify(gitInfo, null, 4)}; +export const BUILD_STAMP = ${JSON.stringify(new Date())}; +/* tslint:enable */ +`, { encoding: 'utf-8' }); + +console.log(`Wrote version info ${gitInfo.raw} to ${relative(resolve(__dirname, '..'), file)}`); diff --git a/LCU.WebApps.External.OnlineLoanApp/wiki/.order b/LCU.WebApps.External.OnlineLoanApp/wiki/.order new file mode 100644 index 0000000..ee154eb --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/wiki/.order @@ -0,0 +1,2 @@ +File-Upload-Component +API-to-Storage-to-Blob-to-ACTion \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/wiki/API-to-Storage-to-Blob-to-ACTion.md b/LCU.WebApps.External.OnlineLoanApp/wiki/API-to-Storage-to-Blob-to-ACTion.md new file mode 100644 index 0000000..e69de29 diff --git a/LCU.WebApps.External.OnlineLoanApp/wiki/File-Upload-Component.md b/LCU.WebApps.External.OnlineLoanApp/wiki/File-Upload-Component.md new file mode 100644 index 0000000..714c44e --- /dev/null +++ b/LCU.WebApps.External.OnlineLoanApp/wiki/File-Upload-Component.md @@ -0,0 +1,203 @@ +# File Upload Component +The file upload component in ```/src/app/shared/file-upload``` allows for files to be uploaded to Azure Blob Storage for a given loan. Once the loan is created in ACTion it will upload the files to ACTion and show up in Attachments. An example of the component shown here: + +![Image alt text](./file-upload-student.png) + +## Enabling Employment Income/Other Income file uploads for a loan +Set ```showProofOfIncomeUploadForm``` value for the loan to true in ```src\app\shared\loan-type.ts``` + +## Adding the upload component to a form (see end of document for an example implementation) +1. Update interface by adding two variables: ```files``` and ```isUploading```, example in ```src\app\loan-wizard\field-groups\personal\personal-information.ts``` +1. Add new values to form group in ```src\app\loan-wizard\field-groups\common-groups.ts```, see ```PERSONAL_INFO``` in this file for an example +1. Add the HTML to the form, example below and in ```src\app\loan-wizard\field-groups\personal\personal-info-form.component.html``` +1. Update TS by adding variables needed, example below and in ```src\app\loan-wizard\field-groups\personal\personal-info-form.component.ts``` +1. Update ```src\assets\i18n\en.json``` to have verbiage for uploading document +1. Update ```src\app\shared\loan-type.ts``` to set ```beforeYouBeginKey``` value on a given loan to the corresponding key in ```en.json``` to enable the checklist +1. Update the summary component to show files if they're present, example in ```src\app\loan-wizard\wizards\common\summary\personal-summary.component.html``` + +## Inputs +1. ```loanId``` _required_ + - The CosmosDB loan ID that uploaded files will be associated with +1. ```section``` _optional_ + - The section that the form is in, e.g. Collateral. + - If left blank, the description in ACTion will not include this. +1. ```sectionId``` _required_ + - The sectionId is used to be more specific, when there are multiple upload components in a single form. + - An example would be the Income section that shows up to 3 upload components for any borrower on the loan, for which the ```sectionId``` is set to ```Primary Borrower-#```, where ```#``` would be which income section it is associated with, 1, 2, or 3. +1. ```description``` _required_ + - Description that is used to set the attachment type in ACTion + - This text will be displayed in the component after "Upload" + - This text can be overridden by passing a value in for the ```label``` input +1. ```label``` _optional_ + - This text will be displayed in the component after "Upload" + - This was added in after the verbiage requirements needed something more custom in certain areas to show something like ```Upload a copy of first mortgage balance statement``` instead of ```Upload Copy of first mortgage balance statement```. + - If ```label``` is not provided, ```description``` will be shown. +1. ```removeAllFilesSubject``` _optional_ + - This input is used by passing in a ```Subject``` and then triggered by calling the ```.next()``` function on the subject, which will remove all files and emit when completed. + - It is meant to be used along with the ```removedAllFiles``` output to notify the parent component that the files have been removed and the form can be updated. + +## Outputs +1. ```sendFiles``` _required_ + - Used to capture when files change and need to be updated on the parent component. +1. ```uploadingFiles``` _required_ + - Used for form validation so that the Next button cannot be pressed while a file is being uploaded or deleted. +1. ```removedAllFiles``` _optional_ + - Used when a form is closed/not needed and the files that were uploaded need to be deleted. + - Once ```removedAllFilesSubject``` is called and completed, this output is emitted so that the form can be updated as needed. + +## Notes +- When the component loads, it creates two requests for the API: one for the files associated with the ```loanId``` and the other for the settings, which controls the file size and types. +- The `````` component should be surround with a ```div``` that controls the width and visibility, e.g.: +```html +
+``` + +## ACTion +Files that are uploaded into ACTion will be given a type: Attachment, Proof of Income, or Title. This is done by using the ```description``` input and mapping it to a type on the backend API defined in ```appsettings.json```. If a description is mistyped or not found in ```appsettings.json```, it will default to Attachment. + +The description within action is created with the inputs: ```section```, ```sectionId```, and ```description```. + +If ```section``` is null or blank, it is not used as shown in the code and screenshot below. + +```csharp +// Code segment from LoanWizardAPI/CRIF.ACTion/Files/AddAppFilesSection.cs +$"{file.Section} - {file.SectionId}: {file.Description}" +$"{file.SectionId}: {file.Description}" +``` + +![Image alt text](./file-upload-action.png) + +# Example Implementation +## HTML +```html +
+ + +
+``` + +## TypeScript +```ts + @Input() + public group: FormGroup; + + public loanId: string; + + public showUploadForm: boolean; + + public removeAllFilesSubject: Subject; + + constructor(private collateralInfoService: CollateralInfoFormService) { + this.loanId = collateralInfoService.getLoanId(); // not real, just an example + this.showUploadForm = true; + this.removeAllFilesSubject = new Subject(); + } + + // Somewhat of a contrived example, but essentially when the form is supposed to be deleted, the .next() should be called. + // Once files are removed, the removedAllFiles output of the component will emit and call the function to complete removing the form handleRemovedAllFiles (below) + public closeForm() { + this.removeAllFilesSubject.next(); + } + + // Sets the files value of the group, called when component is initialized and when all files have been removed. + public handleFiles(files: any[]): void { + this.group.patchValue({ 'files': files }); + } + + // Sets a boolean to true/false for form validation so that Next is disabled + public handleUploadingFiles(isUploading: boolean): void { + this.group.patchValue({ 'isUploading': isUploading }); + } + + public handleRemovedAllFiles(removedAllFiles: boolean): void { + if(removedAllFiles) { + // Code to clear/delete form here... + } + } +``` + + +## Interface +This is the interface that the files variable should be added to. If it is in the Personal Info section, it would be added to ```src\app\loan-wizard\field-groups\personal\personal-information.ts``` + +```ts +{ + ... + // not all variables shown + files?: any[]; + isUploading?: boolean; + ... +} +``` + +## src\app\loan-wizard\field-groups\common-groups.ts +This file is where the form group for the page is generated. It will also need to have both variables added to the correct section so they can be used with the validators. +```ts +{ + ... + // not all variables shown + { + field: 'files', + validation: [WizardValidators.FileValidator] + }, + { + field: 'isUploading', + validation: [WizardValidators.MustBeFalse] + } + ... +} +``` + +## src\assets\i18n\en.json +```ts +{ + ... + // not all variables shown + "BEFOREYOUBEGIN": { + "EXAMPLE": { + "INTRO": "Before you begin the process, you should have the following documents:", + "HEADINGS": [ + { + "HEADING": "Documents to Upload", + "VALUES": [ + "Proof of income (i.e. most recent payroll check stub, 1040s for self-employed borrowers, bank statement).", + "Proof that you are a student (i.e. Student ID from current school year or current enrollment documentation such as a class schedule)." + ] + } + ] + }, + ... + } +} +``` + +## src\app\shared\loan-type.ts +Add needed keys for checklist component, coming from en.json (see above) +```js + { + ... + // not all variables shown + beforeYouBeginKey: 'BEFOREYOUBEGIN.EXAMPLE', // Should be added/updated whenever the upload form is added + showProofOfIncomeUploadForm: true // Set to true if the proof of income file uploads should show, be sure to update en.json! + ... + } +``` + +## Summary component +Add ```divs``` to display files. The description can be pulled from the first file, as shown (it is done with income files as there are multiple sections and borrowers), or the description can be hard coded. The files array is passed into the ```uploadedFiles``` pipe which parses them into a comma-separated list. The pipe is located at ```src\app\shared\pipes\uploaded-files.pipe.ts``` +```html +
+ +
{{personalInformation.files[0].description}} document(s)
+
{{personalInformation.files | uploadedFiles}}
+
+``` \ No newline at end of file diff --git a/LCU.WebApps.External.OnlineLoanApp/wiki/file-upload-action.png b/LCU.WebApps.External.OnlineLoanApp/wiki/file-upload-action.png new file mode 100644 index 0000000000000000000000000000000000000000..f45c0b433707a56463ad0cf982ad2be23f495df2 GIT binary patch literal 20300 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