commit
f85ff50112
338 changed files with 33537 additions and 6106 deletions
18
.eslintrc.js
18
.eslintrc.js
|
|
@ -4,11 +4,12 @@ module.exports = {
|
|||
jest: true
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 14
|
||||
ecmaVersion: 'latest'
|
||||
},
|
||||
extends: [
|
||||
'eslint-config-airbnb-base',
|
||||
'plugin:vue/vue3-recommended'
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:jsdoc/recommended'
|
||||
],
|
||||
rules: {
|
||||
'linebreak-style': 'off',
|
||||
|
|
@ -17,11 +18,11 @@ module.exports = {
|
|||
'vue/attribute-hyphenation': ['warn', 'never'],
|
||||
'vue/v-on-event-hyphenation': ['warn', 'never'],
|
||||
'object-curly-newline': ['error', { consistent: true }],
|
||||
'function-paren-newline': ['error', 'never'],
|
||||
'operator-linebreak': ['error', 'before'],
|
||||
'function-paren-newline': ['error', 'multiline'],
|
||||
'operator-linebreak': ['error', 'before', { overrides: { '=': 'after' } }],
|
||||
'implicit-arrow-linebreak': ['off'],
|
||||
'comma-dangle': ['error', 'never'],
|
||||
indent: ['error', 4],
|
||||
indent: ['error', 4, { SwitchCase: 1 }],
|
||||
'max-len': ['error', { code: 140 }],
|
||||
'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
|
||||
'vue/html-indent': 'off',
|
||||
|
|
@ -29,6 +30,10 @@ module.exports = {
|
|||
singleline: 'never',
|
||||
multiline: 'never'
|
||||
}],
|
||||
'jsdoc/check-tag-names': ['error', {
|
||||
definedTags: ['store', 'endpoint', 'category', 'subcategory', 'remarks']
|
||||
}],
|
||||
'jsdoc/require-jsdoc': 0,
|
||||
'vue/html-self-closing': ['error', {
|
||||
html: {
|
||||
void: 'any',
|
||||
|
|
@ -38,7 +43,8 @@ module.exports = {
|
|||
svg: 'always',
|
||||
math: 'always'
|
||||
}],
|
||||
'import/extensions': ['error', 'always', { vue: 'never', js: 'ignorePackages' }]
|
||||
'import/extensions': ['error', 'always', { js: 'ignorePackages' }],
|
||||
'no-param-reassign': ['error', { props: true, ignorePropertyModificationsFor: ['item'] }]
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
|
|
|
|||
|
|
@ -85,10 +85,17 @@ stages:
|
|||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
cfDistributionId: $(cfDistributionId)
|
||||
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(devDeploymentProfile)
|
||||
|
||||
# Test Build/Deploy
|
||||
- stage: Test
|
||||
|
|
@ -129,9 +136,17 @@ stages:
|
|||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(sysDeploymentProfile)
|
||||
|
||||
# QA Build/Deploy
|
||||
- stage: QA
|
||||
|
|
@ -172,9 +187,11 @@ stages:
|
|||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
|
|
@ -184,7 +201,7 @@ stages:
|
|||
|
||||
# Prod Build/Deploy
|
||||
- stage: Prod
|
||||
condition: succeeded('Qa')
|
||||
condition: succeeded('QA')
|
||||
variables:
|
||||
- group: ISS-Prod
|
||||
jobs:
|
||||
|
|
@ -221,9 +238,11 @@ stages:
|
|||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
__VUE_APP_GOOGLE_MAPS_API_SCRIPT__: $(__VUE_APP_GOOGLE_MAPS_API_SCRIPT__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
|
|
|
|||
|
|
@ -1,23 +1,26 @@
|
|||
module.exports = {
|
||||
verbose: true,
|
||||
coverageReporters: ["html", "text", "cobertura"],
|
||||
reporters: ["default", "jest-junit"],
|
||||
testResultsProcessor: "jest-junit",
|
||||
preset: "@vue/cli-plugin-unit-jest",
|
||||
transform: { "^.+\\.vue$": "@vue/vue3-jest" },
|
||||
moduleFileExtensions: ["js", "vue"],
|
||||
collectCoverageFrom: [
|
||||
"src/**/*.{js,vue}",
|
||||
"!src/main.js",
|
||||
"!src/constants/*.js",
|
||||
"!src/router/**/*.js",
|
||||
"!src/helpers/unit-test-helper.js"
|
||||
verbose: true,
|
||||
coverageReporters: ['html', 'text', 'cobertura'],
|
||||
reporters: ['default', 'jest-junit'],
|
||||
testResultsProcessor: 'jest-junit',
|
||||
preset: '@vue/cli-plugin-unit-jest',
|
||||
transform: { '^.+\\.vue$': '@vue/vue3-jest' },
|
||||
moduleFileExtensions: ['js', 'vue'],
|
||||
moduleNameMapper: {
|
||||
axios: 'axios/dist/browser/axios.cjs'
|
||||
},
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{js,vue}',
|
||||
'!src/main.js',
|
||||
'!src/constants/*.js',
|
||||
'!src/router/**/*.js',
|
||||
'!src/helpers/unit-test-helper.js'
|
||||
// END
|
||||
], // ! means exclude from coverage.
|
||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
coverageThreshold: {
|
||||
// global: {
|
||||
// statements: 80,
|
||||
// },
|
||||
},
|
||||
], // ! means exclude from coverage.
|
||||
testMatch: ['**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'],
|
||||
coverageThreshold: {
|
||||
// global: {
|
||||
// statements: 80,
|
||||
// },
|
||||
}
|
||||
};
|
||||
|
|
|
|||
1940
package-lock.json
generated
1940
package-lock.json
generated
File diff suppressed because it is too large
Load diff
34
package.json
34
package.json
|
|
@ -11,42 +11,54 @@
|
|||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"test:unit": "vue-cli-service test:unit --coverage --ci",
|
||||
"test:unit": "vue-cli-service test:unit --coverage --ci --colors",
|
||||
"test:unit:lite": "vue-cli-service test:unit --ci"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"bootstrap": "^5.2.3",
|
||||
"maska": "^1.5.0",
|
||||
"pinia": "^2.0.22",
|
||||
"axios": "^1.4.0",
|
||||
"axios-retry": "^3.5.0",
|
||||
"bootstrap": "^5.3",
|
||||
"iframe-resizer": "^4.3.9",
|
||||
"maska": "^2.1.1",
|
||||
"pinia": "^2.1.4",
|
||||
"pinia-plugin-persistedstate": "^2.2.0",
|
||||
"vee-validate": "^4.7.0",
|
||||
"uuid": "^9.0.1",
|
||||
"vee-validate": "^4.5.7",
|
||||
"vue": "^3.3.4",
|
||||
"vue-plugin-load-script": "^2.1.0",
|
||||
"vue-router": "4.1.3"
|
||||
"vue-router": "4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pinia/testing": "0.1.2",
|
||||
"@rushstack/eslint-patch": "^1.3.2",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@testing-library/vue": "6.6.1",
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"@vitest/coverage-v8": "^0.34.1",
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-unit-jest": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"@vue/test-utils": "^2.0.0-0",
|
||||
"@vue/test-utils": "^2.4.1",
|
||||
"@vue/vue3-jest": "^27.0.0-alpha.1",
|
||||
"axios-mock-adapter": "^1.21.5",
|
||||
"babel-jest": "^27.0.6",
|
||||
"eslint": "8.29.0",
|
||||
"eslint": "8.45.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-import-resolver-alias": "1.1.2",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-jsdoc": "^46.4.3",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"jest": "^27.0.5",
|
||||
"jest-junit": "^13.0.0",
|
||||
"jest-serializer-vue": "^3.1.0",
|
||||
"jsdoc": "^4.0.2",
|
||||
"jsdom": "^22.1.0",
|
||||
"sass": "^1.32.7",
|
||||
"sass-loader": "^12.0.0",
|
||||
"vitest": "^0.32.4"
|
||||
"vite": "^4.4.6",
|
||||
"vitest": "^0.33.0",
|
||||
"volar-service-vetur": "latest"
|
||||
}
|
||||
}
|
||||
}
|
||||
13
public/css/README.md
Normal file
13
public/css/README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Using Sass in this folder
|
||||
|
||||
This public/css folder uses a standalone .scss > .css setup.
|
||||
## Usage
|
||||
|
||||
From a Terminal window, run the Sass Watch command from the public folder only:
|
||||
|
||||
```bash
|
||||
sass --no-source-map --watch scss:css
|
||||
```
|
||||
This will compile and output .css files (into the public/css folder) that can be consumed by the Payment page.
|
||||
|
||||
# Do not use the .css files on their own. You must use the .scss files and have Sass compile them to .css files.
|
||||
205
public/css/hop-styling.css
Normal file
205
public/css/hop-styling.css
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
body {
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
background-color: white;
|
||||
padding: 8px;
|
||||
}
|
||||
body .validation-info {
|
||||
display: none;
|
||||
}
|
||||
body .italicSmall {
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
}
|
||||
body span {
|
||||
line-height: 24px;
|
||||
}
|
||||
body a {
|
||||
color: #1574A1;
|
||||
text-decoration: none;
|
||||
}
|
||||
body .form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
body .headerlinecontainer .headerline1 {
|
||||
font-size: 20px;
|
||||
line-height: 32px;
|
||||
font-weight: 400;
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
body input,
|
||||
body select {
|
||||
margin-top: 4px;
|
||||
}
|
||||
body input,
|
||||
body select {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #8E9292;
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
}
|
||||
body input:focus, body input:focus-visible,
|
||||
body select:focus,
|
||||
body select:focus-visible {
|
||||
box-shadow: 0 0 0 2.5px #1574a1;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
}
|
||||
body input:disabled, body input.disabled,
|
||||
body select:disabled,
|
||||
body select.disabled {
|
||||
background-color: #f5f5f5;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
body input:disabled:hover, body input.disabled:hover,
|
||||
body select:disabled:hover,
|
||||
body select.disabled:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid #8e9292;
|
||||
}
|
||||
body input:hover,
|
||||
body select:hover {
|
||||
border: 1px solid #8e9292;
|
||||
box-shadow: 0 0 0 4px #9fcee6;
|
||||
}
|
||||
body select {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #8E9292;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 95% 50%;
|
||||
background-size: 15px 9px;
|
||||
appearance: none;
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
}
|
||||
body .creditCardSpecific div {
|
||||
padding: 8px 0;
|
||||
}
|
||||
body .creditCardSpecific div .headerlinecontainer {
|
||||
display: none;
|
||||
}
|
||||
body .creditCardSpecific div label {
|
||||
font-weight: 500;
|
||||
}
|
||||
body .creditCardSpecific #infoRow2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
body #cardExpirationContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
}
|
||||
@media (min-width: 400px) {
|
||||
body #cardExpirationContainer {
|
||||
flex-direction: row;
|
||||
}
|
||||
body #cardExpirationContainer .card_expirationYear,
|
||||
body #cardExpirationContainer .card_expirationMonth {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
body #cardExpirationContainer .card_expirationYear {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
body .optional-label span {
|
||||
display: none;
|
||||
}
|
||||
body .optional-label:after {
|
||||
content: " (optional)";
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-size: 15px;
|
||||
}
|
||||
body .buttonContainer {
|
||||
height: 48px;
|
||||
}
|
||||
body .buttonContainer button {
|
||||
position: relative;
|
||||
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
height: 48px;
|
||||
}
|
||||
@media (hover: hover) {
|
||||
body .buttonContainer button {
|
||||
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
|
||||
}
|
||||
}
|
||||
body .buttonContainer button:focus {
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||
}
|
||||
body .buttonContainer button:focus, body .buttonContainer button:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||
color: #fff;
|
||||
background: linear-gradient(270deg, rgb(6, 87, 124) 0%, rgb(6, 87, 124) 100%);
|
||||
}
|
||||
body .buttonContainer button:disabled {
|
||||
background: #e3e4e4 !important;
|
||||
background: linear-gradient(270deg, #e3e4e4 0%, #e3e4e4 100%) !important;
|
||||
color: #4d5151 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
body .buttonContainer button.has-loader {
|
||||
color: #fff;
|
||||
background: #06577c;
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||
pointer-events: auto;
|
||||
}
|
||||
body .buttonContainer button.delay {
|
||||
transition: background 0s 0s ease-in-out;
|
||||
}
|
||||
body .has-error input,
|
||||
body .has-error select {
|
||||
border: 1px solid #d4281c;
|
||||
}
|
||||
body .has-error input:focus,
|
||||
body .has-error select:focus {
|
||||
box-shadow: 0 0 0 2.5px #fcbfbb;
|
||||
}
|
||||
body .has-error input:hover,
|
||||
body .has-error select:hover {
|
||||
border: 1px solid #d4281c;
|
||||
box-shadow: 0 0 0 4px #fcbfbb;
|
||||
}
|
||||
body .has-error select {
|
||||
border: 1px solid #d4281c;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%23d4281c'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 95% 50%;
|
||||
background-size: 15px 9px;
|
||||
appearance: none;
|
||||
}
|
||||
body .has-error small {
|
||||
margin-top: 4px;
|
||||
color: #d4281c;
|
||||
}
|
||||
body .cc-error-container #alert-message {
|
||||
background-color: #fcbfbb;
|
||||
border: 1px solid #d4281c;
|
||||
}
|
||||
114
public/css/site-2020Styling.css
Normal file
114
public/css/site-2020Styling.css
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* Style the modal when PayPal button is selected */
|
||||
#pageLoadingModal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1040;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
}
|
||||
#pageLoadingModal .modal-content {
|
||||
margin: auto auto;
|
||||
}
|
||||
#pageLoadingModal p {
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
}
|
||||
#pageLoadingModal .modal {
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1040;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
outline: 0;
|
||||
}
|
||||
#pageLoadingModal .modal-content {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
background-clip: padding-box;
|
||||
outline: 0;
|
||||
width: 176px;
|
||||
height: 136px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 96px;
|
||||
}
|
||||
#pageLoadingModal .modal-content:after {
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
width: 5.5rem;
|
||||
height: 5.5rem;
|
||||
border: 0.45em solid #d4281c;
|
||||
border-right-color: transparent;
|
||||
animation: spinnerrotate 0.75s linear infinite;
|
||||
}
|
||||
@keyframes spinnerrotate {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
#pageLoadingModal .modal-content p {
|
||||
display: none;
|
||||
}
|
||||
#pageLoadingModal .modal-backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background-color: #000000;
|
||||
}
|
||||
#pageLoadingModal .modal-backdrop.fade {
|
||||
opacity: 0;
|
||||
filter: alpha(opacity=0);
|
||||
}
|
||||
#pageLoadingModal.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.buy-backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
.buy-backdrop #afterpay__iframe-checkout-container {
|
||||
width: 100%;
|
||||
}
|
||||
.buy-backdrop #afterpay__iframe-checkout-container .style_closeWrapper__lIHJe {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.buy-container-closer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.creditCardSpecific {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
window.dataLayer = [{}];
|
||||
</script>
|
||||
<script><%= process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY %></script>
|
||||
<script><%= process.env.VUE_APP_GOOGLE_MAPS_API_SCRIPT %></script>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
|
|
|
|||
224
public/scss/hop-styling.scss
Normal file
224
public/scss/hop-styling.scss
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
body {
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
background-color: white;
|
||||
padding: 8px;
|
||||
|
||||
.validation-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.italicSmall {
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
span {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1574A1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.headerlinecontainer {
|
||||
.headerline1 {
|
||||
font-size: 20px;
|
||||
line-height: 32px;
|
||||
font-weight: 400;
|
||||
margin: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #8E9292;
|
||||
padding: 16px;
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 2.5px #1574a1;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: #f5f5f5;
|
||||
filter: grayscale(100%);
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid #8e9292;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid #8e9292;
|
||||
box-shadow: 0 0 0 4px #9fcee6;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #8E9292;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 95% 50%;
|
||||
background-size: 15px 9px;
|
||||
appearance: none;
|
||||
font-size: 16px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.creditCardSpecific {
|
||||
div {
|
||||
padding: 8px 0;
|
||||
.headerlinecontainer {
|
||||
display: none;
|
||||
}
|
||||
label {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
#infoRow2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#cardExpirationContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
@media (min-width: 400px) {
|
||||
flex-direction: row;
|
||||
.card_expirationYear,
|
||||
.card_expirationMonth {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
.card_expirationYear {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.optional-label {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
&:after {
|
||||
content: " (optional)";
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
height: 48px;
|
||||
button {
|
||||
position: relative;
|
||||
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
height: 48px;
|
||||
@media (hover: hover) {
|
||||
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||
}
|
||||
&:focus, // Mouse, touch, stylus focus
|
||||
&:focus-visible {
|
||||
// Keyboard focus for accessibility
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||
color: #fff;
|
||||
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
|
||||
}
|
||||
&:disabled {
|
||||
background: #e3e4e4 !important;
|
||||
background: linear-gradient(270deg, #e3e4e4 0%, #e3e4e4 100%) !important;
|
||||
color: #4d5151 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
&.has-loader {
|
||||
color: #fff;
|
||||
background: #06577c;
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
|
||||
pointer-events: auto;
|
||||
}
|
||||
&.delay {
|
||||
// fixes flicker while transitioning between states
|
||||
transition: background 0s 0s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Error Styling
|
||||
.has-error {
|
||||
input,
|
||||
select {
|
||||
border: 1px solid #d4281c;
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px #fcbfbb;
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid #d4281c;
|
||||
box-shadow: 0 0 0 4px #fcbfbb;
|
||||
}
|
||||
}
|
||||
select {
|
||||
border: 1px solid #d4281c;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%23d4281c'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 95% 50%;
|
||||
background-size: 15px 9px;
|
||||
appearance: none;
|
||||
}
|
||||
small {
|
||||
margin-top: 4px;
|
||||
color: #d4281c;
|
||||
}
|
||||
}
|
||||
|
||||
.cc-error-container {
|
||||
#alert-message {
|
||||
background-color: #fcbfbb;
|
||||
border: 1px solid #d4281c;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
118
public/scss/site-2020Styling.scss
Normal file
118
public/scss/site-2020Styling.scss
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
|
||||
|
||||
/* Style the modal when PayPal button is selected */
|
||||
#pageLoadingModal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1040;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.40);
|
||||
display: flex;
|
||||
|
||||
.modal-content {
|
||||
margin: auto auto;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1040;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
background-clip: padding-box;
|
||||
outline: 0;
|
||||
width: 176px;
|
||||
height: 136px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: 96px;
|
||||
&:after {
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
width: 5.5rem;
|
||||
height: 5.5rem;
|
||||
border: 0.45em solid #d4281c;
|
||||
border-right-color: transparent;
|
||||
animation: spinnerrotate 0.75s linear infinite;
|
||||
}
|
||||
@keyframes spinnerrotate {
|
||||
0% { transform: rotate(0)}
|
||||
100% { transform: rotate(360deg)}
|
||||
}
|
||||
p {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background-color: #000000;
|
||||
&.fade {
|
||||
opacity: 0;
|
||||
filter: alpha(opacity=0);
|
||||
}
|
||||
}
|
||||
|
||||
&.hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// AfterPay Styles
|
||||
|
||||
.buy-backdrop {
|
||||
background-color: rgba(0,0,0,.3)!important;
|
||||
#afterpay__iframe-checkout-container {
|
||||
width: 100%;
|
||||
.style_closeWrapper__lIHJe {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buy-container-closer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Modal Spinner
|
||||
|
||||
@keyframes rotate {
|
||||
0% { transform: rotate(0)}
|
||||
100% { transform: rotate(360deg)}
|
||||
}
|
||||
|
||||
.creditCardSpecific {
|
||||
display: none;
|
||||
}
|
||||
32
src/App.vue
32
src/App.vue
|
|
@ -1,12 +1,42 @@
|
|||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in">
|
||||
<transition
|
||||
:duration="{ enter: 200, leave: 200 }"
|
||||
name="route-fade"
|
||||
mode="out-in">
|
||||
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
<loadingModal
|
||||
:showLoader="shouldShowLoader"
|
||||
:showTextCarousel="shouldShowTextCarousel" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import loadingModal from '@/iss-components/loading-modal/loading-modal.vue';
|
||||
import showIssLoadingModal from '@/helpers/loading-modal-helper.js';
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
loadingModal
|
||||
},
|
||||
setup() {
|
||||
const { globalNonpersistedState } = showIssLoadingModal();
|
||||
return { globalNonpersistedState };
|
||||
},
|
||||
computed: {
|
||||
shouldShowLoader() {
|
||||
return this.globalNonpersistedState.showLoadingModal;
|
||||
},
|
||||
shouldShowTextCarousel() {
|
||||
return this.globalNonpersistedState.showTextCarousel;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./node_modules/bootstrap/scss/bootstrap";
|
||||
@import "@/styles/common-styles.scss";
|
||||
|
|
|
|||
14
src/assets/img/icons/Google.svg
Normal file
14
src/assets/img/icons/Google.svg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g fill-rule="nonzero">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#FBBB00" d="M4.432 12.086l-.696 2.6-2.544.053C.432 13.329 0 11.715 0 10c0-1.658.403-3.222 1.118-4.599l2.266.415.992 2.252c-.207.606-.32 1.256-.32 1.932 0 .734.132 1.437.376 2.086z" transform="translate(-119 -800) translate(104 753) translate(15 47)"/>
|
||||
<path fill="#518EF8" d="M19.825 8.132C19.94 8.737 20 9.362 20 10c0 .716-.075 1.414-.219 2.088-.486 2.292-1.758 4.294-3.52 5.71h-.001l-2.853-.146-.404-2.52c1.169-.686 2.083-1.76 2.564-3.044H10.22V8.132h9.605z" transform="translate(-119 -800) translate(104 753) translate(15 47)"/>
|
||||
<path fill="#28B446" d="M16.26 17.798C14.547 19.176 12.37 20 10 20c-3.808 0-7.12-2.129-8.808-5.261l3.24-2.653c.845 2.254 3.02 3.859 5.568 3.859 1.096 0 2.122-.297 3.003-.814l3.257 2.667z" transform="translate(-119 -800) translate(104 753) translate(15 47)"/>
|
||||
<path fill="#F14336" d="M16.383 2.302l-3.24 2.652c-.911-.57-1.989-.899-3.143-.899-2.607 0-4.821 1.678-5.624 4.013L1.12 5.401C2.781 2.192 6.134 0 10 0c2.426 0 4.651.864 6.383 2.302z" transform="translate(-119 -800) translate(104 753) translate(15 47)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
16
src/assets/img/icons/Outlook.com.svg
Normal file
16
src/assets/img/icons/Outlook.com.svg
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="20" viewBox="0 0 22 20">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g fill="#0072C6" fill-rule="nonzero">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M12.794 4.525v3.677l1.37.809c.036.01.114.01.15 0l5.899-3.728c0-.441-.44-.758-.687-.758h-6.732z" transform="translate(-119 -870) translate(104 753) translate(15 117)"/>
|
||||
<path d="M12.794 9.574l1.25.805c.176.121.389 0 .389 0-.212.121 5.78-3.61 5.78-3.61v6.757c0 .736-.502 1.044-1.067 1.044h-6.353V9.574z" transform="translate(-119 -870) translate(104 753) translate(15 117)"/>
|
||||
<g>
|
||||
<path d="M6.266 7.736c-.427 0-.766.187-1.017.562-.25.375-.376.871-.376 1.489 0 .627.125 1.122.376 1.487.25.365.58.546.986.546.42 0 .753-.177.998-.532.246-.354.37-.846.37-1.475 0-.656-.12-1.166-.359-1.531-.238-.364-.564-.546-.978-.546z" transform="translate(-119 -870) translate(104 753) translate(15 117) translate(.052 .143)"/>
|
||||
<path d="M.332 2.515v14.383l11.671 2.295V.397L.333 2.515zm7.81 9.623c-.493.609-1.136.914-1.93.914-.773 0-1.403-.295-1.89-.885-.486-.59-.73-1.358-.73-2.305 0-1 .247-1.81.741-2.427.495-.618 1.15-.927 1.966-.927.77 0 1.393.295 1.869.887.476.591.713 1.37.713 2.338.001.995-.246 1.797-.739 2.405z" transform="translate(-119 -870) translate(104 753) translate(15 117) translate(.052 .143)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
16
src/assets/img/icons/Outlook.svg
Normal file
16
src/assets/img/icons/Outlook.svg
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g fill="#DBA702" fill-rule="nonzero">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M12.394 4.525v3.677l1.327.809c.035.01.111.01.146 0l5.714-3.728c0-.441-.425-.758-.665-.758h-6.522z" transform="translate(-119 -835) translate(104 753) translate(15 82)"/>
|
||||
<path d="M12.394 9.574l1.211.805c.171.121.377 0 .377 0-.205.121 5.6-3.61 5.6-3.61v6.757c0 .736-.487 1.044-1.034 1.044h-6.155V9.574z" transform="translate(-119 -835) translate(104 753) translate(15 82)"/>
|
||||
<g>
|
||||
<path d="M6.07 7.736c-.413 0-.742.187-.985.562-.243.375-.365.871-.365 1.489 0 .627.122 1.122.365 1.487s.562.546.956.546c.406 0 .728-.177.966-.532.238-.354.358-.846.358-1.475 0-.656-.116-1.166-.347-1.531-.231-.364-.547-.546-.948-.546z" transform="translate(-119 -835) translate(104 753) translate(15 82) translate(.05 .143)"/>
|
||||
<path d="M.322 2.515v14.383l11.306 2.295V.397L.322 2.515zm7.566 9.623c-.478.609-1.101.914-1.87.914-.75 0-1.359-.295-1.83-.885-.472-.59-.709-1.358-.709-2.305 0-1 .24-1.81.719-2.427.48-.618 1.114-.927 1.904-.927.746 0 1.35.295 1.81.887.461.591.692 1.37.692 2.338 0 .995-.239 1.797-.716 2.405z" transform="translate(-119 -835) translate(104 753) translate(15 82) translate(.05 .143)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
13
src/assets/img/icons/Yahoo.svg
Normal file
13
src/assets/img/icons/Yahoo.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="20" viewBox="0 0 22 20">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g fill="#5F01D1" fill-rule="nonzero">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M0 4.868h4.29l2.498 6.39 2.53-6.39h4.177l-6.29 15.129H3.003l1.722-4.01L0 4.869zm18.358 5.106H13.68L17.832 0h4.661l-4.135 9.974zm-3.452.955c1.435 0 2.599 1.163 2.599 2.599 0 1.435-1.164 2.598-2.6 2.598-1.434 0-2.598-1.163-2.598-2.598 0-1.436 1.164-2.6 2.599-2.6z" transform="translate(-119 -905) translate(104 753) translate(15 152) translate(0 .11)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 708 B |
11
src/assets/img/icons/add-to-calendar.svg
Normal file
11
src/assets/img/icons/add-to-calendar.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_14504_113611)">
|
||||
<path d="M14.1762 1.52764H13.7656V1.50352C13.7656 1.10476 13.6076 0.722334 13.3263 0.44037C13.0451 0.158406 12.6636 0 12.2659 0C11.8681 0 11.4866 0.158406 11.2054 0.44037C10.9241 0.722334 10.7661 1.10476 10.7661 1.50352V1.52764H5.42476V1.50352C5.42476 1.10476 5.26675 0.722334 4.9855 0.44037C4.70424 0.158406 4.32277 0 3.92501 0C3.52725 0 3.14579 0.158406 2.86453 0.44037C2.58327 0.722334 2.42526 1.10476 2.42526 1.50352V1.52764H1.82376C1.34046 1.52891 0.877316 1.72195 0.53557 2.06455C0.193824 2.40716 0.00127018 2.87146 0 3.35598V14.1717C0.0016909 14.656 0.194379 15.1201 0.536035 15.4626C0.87769 15.8051 1.34059 15.9983 1.82376 16H14.1746C14.6581 15.9987 15.1214 15.8057 15.4634 15.4632C15.8054 15.1206 15.9983 14.6563 16 14.1717V3.35598C15.9987 2.87146 15.8062 2.40716 15.4644 2.06455C15.1227 1.72195 14.6595 1.52891 14.1762 1.52764ZM11.8889 1.50352C11.8889 1.4033 11.9286 1.30718 11.9993 1.23631C12.07 1.16544 12.1659 1.12563 12.2659 1.12563C12.3658 1.12563 12.4617 1.16544 12.5324 1.23631C12.6031 1.30718 12.6428 1.4033 12.6428 1.50352V2.99899C12.6428 3.09922 12.6031 3.19534 12.5324 3.2662C12.4617 3.33707 12.3658 3.37688 12.2659 3.37688C12.1659 3.37688 12.07 3.33707 11.9993 3.2662C11.9286 3.19534 11.8889 3.09922 11.8889 2.99899V1.50352ZM3.54807 1.50352C3.54807 1.4033 3.58778 1.30718 3.65847 1.23631C3.72916 1.16544 3.82504 1.12563 3.92501 1.12563C4.02498 1.12563 4.12086 1.16544 4.19155 1.23631C4.26224 1.30718 4.30195 1.4033 4.30195 1.50352V2.99899C4.30195 3.09922 4.26224 3.19534 4.19155 3.2662C4.12086 3.33707 4.02498 3.37688 3.92501 3.37688C3.82504 3.37688 3.72916 3.33707 3.65847 3.2662C3.58778 3.19534 3.54807 3.09922 3.54807 2.99899V1.50352ZM14.8772 14.1717C14.8747 14.3573 14.8001 14.5345 14.6691 14.6658C14.5382 14.797 14.3614 14.8719 14.1762 14.8744H1.82536C1.63995 14.8723 1.4627 14.7976 1.33144 14.6663C1.20018 14.5351 1.12531 14.3575 1.12281 14.1717V6.59296H14.8772V14.1717Z" fill="#327289"/>
|
||||
<path d="M2.33063 11.282H3.93464V12.6006C3.93464 12.7499 3.99379 12.8931 4.09907 12.9986C4.20435 13.1041 4.34715 13.1634 4.49604 13.1634C4.64494 13.1634 4.78773 13.1041 4.89301 12.9986C4.9983 12.8931 5.05745 12.7499 5.05745 12.6006V11.282H7.46346V12.6006C7.46346 12.7499 7.52261 12.8931 7.62789 12.9986C7.73318 13.1041 7.87597 13.1634 8.02486 13.1634C8.17376 13.1634 8.31655 13.1041 8.42184 12.9986C8.52712 12.8931 8.58627 12.7499 8.58627 12.6006V11.282H10.9923V12.6006C10.9923 12.7499 11.0514 12.8931 11.1567 12.9986C11.262 13.1041 11.4048 13.1634 11.5537 13.1634C11.7026 13.1634 11.8454 13.1041 11.9507 12.9986C12.0559 12.8931 12.1151 12.7499 12.1151 12.6006V11.282H13.7191C13.868 11.282 14.0108 11.2227 14.1161 11.1172C14.2214 11.0116 14.2805 10.8685 14.2805 10.7192C14.2805 10.57 14.2214 10.4268 14.1161 10.3213C14.0108 10.2157 13.868 10.1564 13.7191 10.1564H12.1151V8.84425C12.1151 8.69498 12.0559 8.55183 11.9507 8.44628C11.8454 8.34073 11.7026 8.28143 11.5537 8.28143C11.4048 8.28143 11.262 8.34073 11.1567 8.44628C11.0514 8.55183 10.9923 8.69498 10.9923 8.84425V10.1628H8.58627V8.84425C8.58627 8.69498 8.52712 8.55183 8.42184 8.44628C8.31655 8.34073 8.17376 8.28143 8.02486 8.28143C7.87597 8.28143 7.73318 8.34073 7.62789 8.44628C7.52261 8.55183 7.46346 8.69498 7.46346 8.84425V10.1628H5.05745V8.84425C5.05745 8.69498 4.9983 8.55183 4.89301 8.44628C4.78773 8.34073 4.64494 8.28143 4.49604 8.28143C4.34715 8.28143 4.20435 8.34073 4.09907 8.44628C3.99379 8.55183 3.93464 8.69498 3.93464 8.84425V10.1628H2.33063C2.18174 10.1628 2.03894 10.2221 1.93366 10.3277C1.82837 10.4332 1.76923 10.5764 1.76923 10.7257C1.76923 10.8749 1.82837 11.0181 1.93366 11.1236C2.03894 11.2292 2.18174 11.2885 2.33063 11.2885V11.282Z" fill="#327289"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_14504_113611">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
11
src/assets/img/icons/iCal.svg
Normal file
11
src/assets/img/icons/iCal.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="20" viewBox="0 0 17 20">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g fill="#606060" fill-rule="nonzero">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M5.44 20C2.463 19.983 0 13.907 0 10.812c0-5.055 3.793-6.161 5.254-6.161.659 0 1.362.258 1.982.487.434.16.883.325 1.132.325.15 0 .501-.14.812-.264.663-.263 1.487-.591 2.447-.591h.006c.717 0 2.89.157 4.198 2.12l.306.46-.44.332c-.63.475-1.778 1.341-1.778 3.058 0 2.032 1.3 2.814 1.925 3.19.276.166.561.337.561.71 0 .245-1.95 5.492-4.782 5.492-.692 0-1.182-.209-1.614-.392-.437-.186-.815-.346-1.438-.346-.316 0-.715.149-1.138.307-.578.216-1.232.461-1.974.461h-.02zM11.927 0C12.001 2.66 10.1 4.504 8.2 4.388 7.887 2.266 10.1 0 11.927 0z" transform="translate(-119 -763) translate(104 753) translate(15 10)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 907 B |
|
|
@ -1,36 +1,36 @@
|
|||
const analyticsPageEvents = {
|
||||
const analyticsPageEvents = Object.freeze({
|
||||
ENTRY: 'ENTRY',
|
||||
EVENT: 'EVENT'
|
||||
};
|
||||
});
|
||||
|
||||
// GA Constants
|
||||
const GaEvents = {
|
||||
const GaEvents = Object.freeze({
|
||||
GENERIC_EVENT: 'event',
|
||||
PAGE_VIEW_EVENT: 'logPageview'
|
||||
};
|
||||
});
|
||||
|
||||
const GaCategories = {
|
||||
const GaCategories = Object.freeze({
|
||||
API_RESPONSE: 'Api_Response',
|
||||
EVOX: 'Evox'
|
||||
};
|
||||
});
|
||||
|
||||
const GaActions = {
|
||||
const GaActions = Object.freeze({
|
||||
RESULT: 'Result',
|
||||
CLICKED: 'Clicked',
|
||||
VIF: 'vif',
|
||||
SUBMITTED: 'Submitted'
|
||||
};
|
||||
});
|
||||
|
||||
const GaLabels = {
|
||||
const GaLabels = Object.freeze({
|
||||
SUCCESS: 'Success',
|
||||
ERROR: 'Error',
|
||||
LICENSE_PLATE_LOOKUP: 'License_Plate_Look_Up',
|
||||
VIN_LOOKUP: 'Vin_Look_Up',
|
||||
ADDRESS_LOOKUP: 'Address_Look_up'
|
||||
};
|
||||
});
|
||||
|
||||
const ValueToLogTypes = {
|
||||
const ValueToLogTypes = Object.freeze({
|
||||
LAST_5: 'last_5'
|
||||
};
|
||||
});
|
||||
|
||||
export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
const applicationConfig = {
|
||||
/**
|
||||
* @module applicationConfig
|
||||
* @author T-Wrecks Team
|
||||
* @copyright Safelite
|
||||
*/
|
||||
const applicationConfig = Object.freeze({
|
||||
CURRENT_ENVIRONMENT: process.env.VUE_APP_CURRENT_ENVIRONMENT, // "Localhost", "Dev", "QA", and "Prod"
|
||||
CONSUMER_CF_DISTRO: process.env.VUE_APP_CONSUMER_CF_DISTRO,
|
||||
ANALYTICS_SESSION_TIMEOUT_MINUTES: 30,
|
||||
|
|
@ -10,8 +15,15 @@ const applicationConfig = {
|
|||
PAGE_QUERYSTRING: 'issPage',
|
||||
CLIENTTAG_QUERYSTRING: 'ClientTag',
|
||||
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
|
||||
SAFELITE_HOP: process.env.VUE_APP_SAFELITE_HOP,
|
||||
ISS_DEV_CMS_DOMAIN: 'https://digitalisscms.dev.safelite.io',
|
||||
CASH_PARENT_ACCOUNT_NUMBER: 167132
|
||||
};
|
||||
CASH_PARENT_ACCOUNT_NUMBER: 167132,
|
||||
ITAC_FAIR_AND_REASONABLE_BILLTO: '214616', // Lib Mutal FnR BillTo
|
||||
ITAC_CASH_BILLTO: '283393', // Lib Mutal ITAC Cash BillTo
|
||||
GOOGLE_CALENDAR: 'https://www.google.com/calendar/render?action=TEMPLATE',
|
||||
YAHOO_CALENDAR: 'https://calendar.yahoo.com/?v=60',
|
||||
OUTLOOK_CALENDAR:
|
||||
'https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent'
|
||||
});
|
||||
|
||||
export { applicationConfig };
|
||||
export default applicationConfig;
|
||||
|
|
|
|||
13
src/constants/bailoutCode.js
Normal file
13
src/constants/bailoutCode.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const bailoutCode = Object.freeze({
|
||||
Unknown: 0,
|
||||
SaveSessionError: 1,
|
||||
VehicleNotFound: 2,
|
||||
VehicleLookupError: 3,
|
||||
CoverageStatementInvalidState: 4,
|
||||
DoNotSeeMyShop: 5,
|
||||
PricingResponseError: 6,
|
||||
TPANotEnabled: 7,
|
||||
RequestCallback: 8
|
||||
});
|
||||
|
||||
export default bailoutCode;
|
||||
54
src/constants/bailoutMessage.js
Normal file
54
src/constants/bailoutMessage.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import bailoutCode from '@/constants/bailoutCode';
|
||||
|
||||
function getItemData(data) {
|
||||
if (data === undefined || data == null) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
if (typeof data === 'string') {
|
||||
return data;
|
||||
}
|
||||
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
|
||||
const bailoutMessage = Object.freeze({
|
||||
unknown: (error) => ({
|
||||
code: bailoutCode.Unknown,
|
||||
message: `An unknown bailout occurred: ${getItemData(error)}`
|
||||
}),
|
||||
saveSessionError: (error) => ({
|
||||
code: bailoutCode.SaveSessionError,
|
||||
message: `An error occurred during save session: ${getItemData(error)}`
|
||||
}),
|
||||
vehicleNotFound: (vin) => ({
|
||||
code: bailoutCode.VehicleNotFound,
|
||||
message: `Failed to find vehicle in system with vin: ${vin}`
|
||||
}),
|
||||
vehicleLookupError: (vin, error) => ({
|
||||
code: bailoutCode.VehicleLookupError,
|
||||
message: `An error occurred looking up Vin: ${vin}. Error: ${getItemData(error)}`
|
||||
}),
|
||||
coverageStatementInvalidState: () => ({
|
||||
code: bailoutCode.CoverageStatementInvalidState,
|
||||
message: 'Coverage Statement has entered an invalid state'
|
||||
}),
|
||||
doNotSeeMyShop: () => ({
|
||||
code: bailoutCode.DoNotSeeMyShop,
|
||||
message: 'User does not see their shop.'
|
||||
}),
|
||||
pricingResponseError: (lineItems, error) => ({
|
||||
code: bailoutCode.PricingResponseError,
|
||||
message: `An error occurred in getPriceOrderItems. Line Items: ${getItemData(lineItems)} Error: ${getItemData(error)}`
|
||||
}),
|
||||
TPANotEnabled: () => ({
|
||||
code: bailoutCode.TPANotEnabled,
|
||||
message: 'User selected TPA when TPA is not enabled for this client'
|
||||
}),
|
||||
RequestCallback: () => ({
|
||||
code: bailoutCode.RequestCallback,
|
||||
message: 'User selected option to receive callback from Safelite'
|
||||
})
|
||||
});
|
||||
|
||||
export default bailoutMessage;
|
||||
8
src/constants/calendar-options.js
Normal file
8
src/constants/calendar-options.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
const calendarOptions = Object.freeze({
|
||||
ICAL: 'iCal',
|
||||
GOOGLE: 'Google',
|
||||
OUTLOOK: 'Outlook',
|
||||
OUTLOOKCOM: 'Outlook.com',
|
||||
YAHOO: 'Yahoo'
|
||||
});
|
||||
export default calendarOptions;
|
||||
7
src/constants/calendar-status.js
Normal file
7
src/constants/calendar-status.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const calendarStatus = Object.freeze({
|
||||
FREE: 'Free',
|
||||
BUSY: 'Busy',
|
||||
TENTATIVE: 'Tentative',
|
||||
OUT_OF_THE_OFFICE: 'OutOfTheOffice'
|
||||
});
|
||||
export default calendarStatus;
|
||||
45
src/constants/cms-widget-fields.js
Normal file
45
src/constants/cms-widget-fields.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
const widgetFields = Object.freeze({
|
||||
HEADER_WIDGET: {
|
||||
NAME: 'Name',
|
||||
ANSWERS: 'Answers'
|
||||
},
|
||||
SUB_HEADER_WIDGET: {
|
||||
NAME: 'Name',
|
||||
SUB_HEADER_TEXT: 'SubHeaderText',
|
||||
SECONDARY_TEXT: 'SecondaryText',
|
||||
BACK_BUTTON_ACCESSIBLE_TEXT: 'BackButtonAccessibleText'
|
||||
},
|
||||
INPUT_QUESTION_WIDGET: {
|
||||
NAME: 'Name',
|
||||
QUESTION_TEXT: 'QuestionText',
|
||||
BUTTON_TEXT: 'ButtonText',
|
||||
ANSWERS: 'Answers'
|
||||
},
|
||||
TEXT_BLOCK_WIDGET: {
|
||||
NAME: 'Name',
|
||||
TEXT: 'Text'
|
||||
},
|
||||
CONTENT_GROUP_WIDGET: {
|
||||
NAME: 'Name',
|
||||
HEADER_TEXT: 'HeaderText',
|
||||
SUBHEADER_TEXT: 'SubheaderText',
|
||||
BODY_TEXT: 'BodyText',
|
||||
BODY_TEXT_2: 'BodyText2',
|
||||
FOOTER_TEXT: 'FooterText',
|
||||
IMAGE: 'Image'
|
||||
},
|
||||
ALERT_WIDGET: {
|
||||
NAME: 'Name',
|
||||
HEADLINE_TEXT: 'HeadlineText',
|
||||
BODY_TEXT: 'BodyText'
|
||||
},
|
||||
FOOTER_WIDGET: {
|
||||
NAME: 'Name',
|
||||
BACK_BUTTON_TEXT: 'BackButtonText',
|
||||
FORWARD_BUTTON_TEXT: 'ForwardButtonText',
|
||||
FOOTER_IMAGE: 'FooterImage',
|
||||
ALT_TEXT: 'AltText'
|
||||
}
|
||||
});
|
||||
|
||||
export default widgetFields;
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
import { applicationConfig } from '@/constants/application-config.js';
|
||||
import applicationConfig from '@/constants/application-config.js';
|
||||
|
||||
const cookieNames = {
|
||||
/**
|
||||
* @module cookieNames
|
||||
* @requires applicationConfig
|
||||
* @author T-Wrecks Team
|
||||
* @copyright Safelite
|
||||
*/
|
||||
const cookieNames = Object.freeze({
|
||||
ISS_SESSION_INFO: `ISSSessionInfo-${applicationConfig.CURRENT_ENVIRONMENT}`,
|
||||
|
||||
// Existing Safelite.com cookies
|
||||
DXDEV: 'dxdev',
|
||||
SESSION_ID: 'sid',
|
||||
SESSION_KEY: 'skey'
|
||||
};
|
||||
});
|
||||
|
||||
export { cookieNames };
|
||||
export default cookieNames;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const coverageStatuses = {
|
||||
PENDING: 'Pending',
|
||||
NO_COMP: 'No Comp',
|
||||
VERIFIED: 'Verified'
|
||||
};
|
||||
const coverageStatuses = Object.freeze({
|
||||
PENDING: 0,
|
||||
NO_COMP: 1,
|
||||
VERIFIED: 2
|
||||
});
|
||||
|
||||
export { coverageStatuses };
|
||||
export default coverageStatuses;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const damageLocationsCms = {
|
||||
const damageLocationsCms = Object.freeze({
|
||||
WINDSHIELD: 'WINDSHIELD',
|
||||
SIDEDOOR: 'SIDEDOOR',
|
||||
REARWINDOW: 'REARWINDOW',
|
||||
DRIVERSIDE: 'DRIVERSIDE',
|
||||
PASSENGERSIDE: 'PASSENGERSIDE'
|
||||
};
|
||||
});
|
||||
|
||||
export { damageLocationsCms };
|
||||
export default damageLocationsCms;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const damageLocationsSelected = {
|
||||
const damageLocationsSelected = Object.freeze({
|
||||
WINDSHIELD: 'Windshield',
|
||||
SIDEDOOR: 'SideDoor',
|
||||
REARWINDOW: 'RearWindow',
|
||||
|
|
@ -16,6 +16,6 @@ const damageLocationsSelected = {
|
|||
PASSENGERSIDE: 'PassengerSide',
|
||||
STATIONARY: 'Stationary',
|
||||
SLIDER: 'Slider'
|
||||
};
|
||||
});
|
||||
|
||||
export { damageLocationsSelected };
|
||||
export default damageLocationsSelected;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
const dynamicStrings = {
|
||||
const dynamicStrings = Object.freeze({
|
||||
GLOBAL_STATE: 'globalState',
|
||||
CUSTOM: 'custom',
|
||||
ROUTER_LINK: 'routerLink:',
|
||||
MODAL_LINK: 'modalLink',
|
||||
TEXT_LINK: 'textLink',
|
||||
EXTERNAL_LINK: 'externalLink'
|
||||
};
|
||||
});
|
||||
|
||||
export { dynamicStrings };
|
||||
export default dynamicStrings;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Example: {custom:glassName}, each key in the array is the value after custom:, like 'glassname'
|
||||
// Please use lowercase only so that we don't have to worry about case sensitivity.
|
||||
|
||||
const customMappings = {
|
||||
const customMappings = Object.freeze({
|
||||
formattedglassname: [
|
||||
{ key: 'Windshield Single', transformedValue: 'windshield' },
|
||||
{ key: 'Windshield Driver', transformedValue: 'driver side split windshield' },
|
||||
|
|
@ -20,12 +20,12 @@ const customMappings = {
|
|||
{ key: 'Passenger Quarter', transformedValue: 'passenger side quarter panel' },
|
||||
{ key: 'Passenger SlideDoor', transformedValue: 'passenger side sliding door' }
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
// Gets an instance of a string where the dynamic portion of the text {custom:KeyName}
|
||||
// is replaced by a value from the above map.
|
||||
// If the value isn't found, return the original dynamic string without replacement
|
||||
export function getCustomTransformValue(dynamicString, key) {
|
||||
const getCustomTransformValue = (dynamicString, key) => {
|
||||
// Get array key from the dynamic string
|
||||
const regexExp = /{(.*?):(.*?)}/g;
|
||||
const matches = [...dynamicString.matchAll(regexExp)];
|
||||
|
|
@ -53,4 +53,6 @@ export function getCustomTransformValue(dynamicString, key) {
|
|||
const finalString = dynamicString.replace(`{custom:${arrayKey}}`, mapObject.transformedValue);
|
||||
|
||||
return finalString;
|
||||
}
|
||||
};
|
||||
|
||||
export default getCustomTransformValue;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const endorsementOptions = {
|
||||
const endorsementOptions = Object.freeze({
|
||||
EDUCATOR: 'Educator',
|
||||
OEM_APPROVED: 'OEM Approved',
|
||||
FULL_GLASS: 'Full Glass Coverage',
|
||||
PARKING_GUARD: 'Parking Guard',
|
||||
REPAIR_WAIVED: 'Repair Waived'
|
||||
};
|
||||
});
|
||||
|
||||
export { endorsementOptions };
|
||||
export default endorsementOptions;
|
||||
|
|
|
|||
|
|
@ -1,135 +1,209 @@
|
|||
const endpoints = {
|
||||
const CONTENT_BASE_URL = '/content/api/v1/content';
|
||||
const LOCATION_BASE_URL = '/location/api/v1/location';
|
||||
const SCHEDULE_BASE_URL = '/schedule/api/v1/schedule';
|
||||
const PARTS_BASE_URL = '/parts/api/v1/parts';
|
||||
const VEHICLE_BASE_URL = '/vehicle/api/v1/vehicle';
|
||||
const PRICE_BASE_URL = '/price/api/v1/price';
|
||||
const ACCOUNT_BASE_URL = '/account/api/v1/account';
|
||||
const EXPERIMENTS_BASE_URL = '/experiments/api/v1/experiments';
|
||||
const ANALYTICS_BASE_URL = '/analytics/api/v1/analytics';
|
||||
const CLIENT_AUTH_BASE_URL = '/clientauth/api/v1/clientauth';
|
||||
const COVERAGE_BASE_URL = '/coverage/api/v1/coverage';
|
||||
const ORDER_BASE_URL = '/order/api/v1/order';
|
||||
|
||||
const endpoints = Object.freeze({
|
||||
GetRouteInfo: {
|
||||
url: (applicationAbbreviation) => `/content/api/v1/content/${applicationAbbreviation}/RouteInfo`,
|
||||
url: (applicationAbbreviation) => `${CONTENT_BASE_URL}/${applicationAbbreviation}/RouteInfo`,
|
||||
method: 'POST'
|
||||
},
|
||||
GetHomepageInfo: {
|
||||
url: (applicationAbbreviation) => `/content/api/v1/content/${applicationAbbreviation}/HomepageInfo`,
|
||||
url: (applicationAbbreviation) => `${CONTENT_BASE_URL}/${applicationAbbreviation}/HomepageInfo`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetPageData: {
|
||||
url: (applicationAbbreviation, pageName) => `/content/api/v1/content/${applicationAbbreviation}/${pageName}`,
|
||||
url: (applicationAbbreviation, pageName) => `${CONTENT_BASE_URL}/${applicationAbbreviation}/${pageName}`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetAlertReasons: {
|
||||
url: `${LOCATION_BASE_URL}/alert-reasons`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetShopTimeSlots: {
|
||||
url: `${SCHEDULE_BASE_URL}/shop-time-slots`,
|
||||
method: 'POST'
|
||||
},
|
||||
GetMobileTimeSlots: {
|
||||
url: `${SCHEDULE_BASE_URL}/mobile-time-slots`,
|
||||
method: 'POST'
|
||||
},
|
||||
GetMobilePremiumFee: {
|
||||
url: `${PARTS_BASE_URL}/mobile-premium-fee`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetVehicleYears: {
|
||||
url: '/vehicle/api/v1/vehicle/years',
|
||||
url: `${VEHICLE_BASE_URL}/years`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetVehicleMakes: {
|
||||
url: '/vehicle/api/v1/vehicle/makes/',
|
||||
url: `${VEHICLE_BASE_URL}/makes/`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetVehicleModels: {
|
||||
url: '/vehicle/api/v1/vehicle/models',
|
||||
url: `${VEHICLE_BASE_URL}/models`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetVehicleStyles: {
|
||||
url: '/vehicle/api/v1/vehicle/styles',
|
||||
url: `${VEHICLE_BASE_URL}/styles`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetDamageOptions: {
|
||||
url: '/parts/api/v1/parts/damage-options',
|
||||
url: `${PARTS_BASE_URL}/damage-options`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetPartsOrQuestions: {
|
||||
url: '/parts/api/v1/parts/parts-or-questions',
|
||||
url: `${PARTS_BASE_URL}/parts-or-questions`,
|
||||
method: 'POST'
|
||||
},
|
||||
GetParts: {
|
||||
url: '/parts/api/v1/parts/parts',
|
||||
url: `${PARTS_BASE_URL}/parts`,
|
||||
method: 'POST'
|
||||
},
|
||||
GetInsurancePriceOrderItems: {
|
||||
url: `${PRICE_BASE_URL}/order-items-with-insurance-pricing`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetPriceOrderItems: {
|
||||
url: '/price/api/v1/price/order-items',
|
||||
url: `${PRICE_BASE_URL}/order-items`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetProviders: {
|
||||
url: `${LOCATION_BASE_URL}/providers`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetCapabilityQuestions: {
|
||||
url: '/parts/api/v1/parts/capability-questions',
|
||||
url: `${PARTS_BASE_URL}/capability-questions`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetPartFromCapabilityAnswer: {
|
||||
url: '/parts/api/v1/parts/part-from-capability-answer',
|
||||
url: `${PARTS_BASE_URL}/part-from-capability-answer`,
|
||||
method: 'POST'
|
||||
},
|
||||
GetWipers: {
|
||||
url: '/parts/api/v1/parts/wipers',
|
||||
url: `${PARTS_BASE_URL}/wipers`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetRainDefense: {
|
||||
url: '/parts/api/v1/parts/rain-defense',
|
||||
url: `${PARTS_BASE_URL}/rain-defense`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetSupportingItems: {
|
||||
url: '/parts/api/v1/parts/supporting-items',
|
||||
url: `${PARTS_BASE_URL}/supporting-items`,
|
||||
method: 'POST'
|
||||
},
|
||||
GetMobileFeePart: {
|
||||
url: `${PARTS_BASE_URL}/mobile-fee`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetServiceabilityDetails: {
|
||||
url: '/location/api/v1/location/serviceability-details',
|
||||
url: `${LOCATION_BASE_URL}/serviceability-details`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetVehicle: {
|
||||
url: '/vehicle/api/v1/vehicle/lookup',
|
||||
url: `${VEHICLE_BASE_URL}/lookup`,
|
||||
method: 'GET'
|
||||
},
|
||||
GetAccountInfo: {
|
||||
url: `${ACCOUNT_BASE_URL}/`,
|
||||
method: 'GET'
|
||||
},
|
||||
TaxOrderItems: {
|
||||
url: `${PRICE_BASE_URL}/taxed-order-items`,
|
||||
method: 'GET'
|
||||
},
|
||||
LogExperimentExposureIfAssigned: {
|
||||
url: '/experiments/api/v1/experiments/log-exposure',
|
||||
url: `${EXPERIMENTS_BASE_URL}/log-exposure`,
|
||||
method: 'POST'
|
||||
},
|
||||
LogPageView: {
|
||||
url: '/analytics/api/v1/analytics/log-page-view',
|
||||
url: `${ANALYTICS_BASE_URL}/log-page-view`,
|
||||
method: 'POST'
|
||||
},
|
||||
LogCustomEvent: {
|
||||
url: '/analytics/api/v1/analytics/log-custom-event',
|
||||
url: `${ANALYTICS_BASE_URL}/log-custom-event`,
|
||||
method: 'POST'
|
||||
},
|
||||
LookupVehicleByVin: {
|
||||
url: '/vehicle/api/v1/vehicle/lookup',
|
||||
url: `${VEHICLE_BASE_URL}/lookup`,
|
||||
method: 'POST'
|
||||
},
|
||||
LookupVinByAddress: {
|
||||
url: '/vehicle/api/v1/vehicle/lookup-vin-by-address',
|
||||
url: `${VEHICLE_BASE_URL}/lookup-vin-by-address`,
|
||||
method: 'POST'
|
||||
},
|
||||
LookupVinByPlate: {
|
||||
url: '/vehicle/api/v1/vehicle/lookup-vin-by-plate',
|
||||
url: `${VEHICLE_BASE_URL}/lookup-vin-by-plate`,
|
||||
method: 'POST'
|
||||
},
|
||||
InitializeSession: {
|
||||
url: '/analytics/api/v1/analytics/initialize',
|
||||
url: `${ANALYTICS_BASE_URL}/initialize`,
|
||||
method: 'POST'
|
||||
},
|
||||
GetExperimentsByUser: {
|
||||
url: '/analytics/api/v1/analytics/get-experiments',
|
||||
url: `${ANALYTICS_BASE_URL}/get-experiments`,
|
||||
method: 'GET'
|
||||
},
|
||||
RunExperimentsForTrigger: {
|
||||
url: '/experiments/api/v1/experiments/run',
|
||||
url: `${EXPERIMENTS_BASE_URL}/run`,
|
||||
method: 'POST'
|
||||
},
|
||||
ValidateZip: {
|
||||
url: '/location/api/v1/location/zip',
|
||||
url: `${LOCATION_BASE_URL}/zip`,
|
||||
method: 'GET'
|
||||
},
|
||||
GooglePlaces: {
|
||||
url: 'https://maps.googleapis.com/maps/api/js?key={apiKey}&libraries=places'
|
||||
url: (apiKey) => `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`
|
||||
},
|
||||
ValidateClientTag: {
|
||||
url: '/clientauth/api/v1/clientauth/validate-client-tag',
|
||||
url: `${CLIENT_AUTH_BASE_URL}/validate-client-tag`,
|
||||
method: 'GET'
|
||||
},
|
||||
ValidateClientSignature: {
|
||||
url: `${CLIENT_AUTH_BASE_URL}/validate-client-signature`,
|
||||
method: 'POST'
|
||||
},
|
||||
IsVinbyAddressPermissible: {
|
||||
url: '/vehicle/api/v1/vehicle/is-vin-by-address-permissible',
|
||||
url: `${VEHICLE_BASE_URL}/is-vin-by-address-permissible`,
|
||||
method: 'Get'
|
||||
},
|
||||
CoveragePolicyInfo: {
|
||||
url: '/coverage/api/v1/coverage/get-policy-information',
|
||||
url: `${COVERAGE_BASE_URL}/policy-information`,
|
||||
method: 'POST'
|
||||
},
|
||||
RegisterClaim: {
|
||||
url: '/coverage/api/v1/coverage/register-claim',
|
||||
url: `${COVERAGE_BASE_URL}/register-claim`,
|
||||
method: 'POST'
|
||||
},
|
||||
SaveSession: {
|
||||
url: `${ORDER_BASE_URL}/save-session/iss`,
|
||||
method: 'POST'
|
||||
},
|
||||
LoadSession: {
|
||||
url: `${ORDER_BASE_URL}/load-session/iss`,
|
||||
method: 'POST'
|
||||
},
|
||||
DuplicateSearch: {
|
||||
// eslint-disable-next-line max-len
|
||||
url: (accountNumber, policyNumber, dateOfLoss) => `${ORDER_BASE_URL}/duplicate-check/${accountNumber}/${policyNumber}/${dateOfLoss}`,
|
||||
method: 'GET'
|
||||
},
|
||||
FinalDeductible: {
|
||||
url: `${COVERAGE_BASE_URL}/final-deductible`,
|
||||
method: 'POST'
|
||||
},
|
||||
GetPaymentSignature: {
|
||||
url: `${ORDER_BASE_URL}/sign`,
|
||||
method: 'POST'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export { endpoints };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
const errorMessages = {
|
||||
/**
|
||||
* @module errorMessages
|
||||
* @author T-Wrecks Team
|
||||
* @copyright Safelite
|
||||
*/
|
||||
const errorMessages = Object.freeze({
|
||||
DAMAGE_LOCATION_REQUIRED: 'Please select damage location',
|
||||
DAMAGE_SIDE_REQUIRED: 'Please select vehicle side',
|
||||
DRIVER_SIDE_OPTIONS_REQUIRED: 'Please select window',
|
||||
|
|
@ -22,20 +27,26 @@ const errorMessages = {
|
|||
SERVICE_ZIP_FORMAT: 'Please enter a valid service ZIP',
|
||||
VIN_REQUIRED: 'Please enter your VIN',
|
||||
VIN_FORMAT:
|
||||
// eslint-disable-next-line max-len
|
||||
'Invalid VIN. Please make sure that you entered the correct 17-digit, alpha-numeric number. VINs do not contain the letters I, O, or Q',
|
||||
OPTION_REQUIRED: 'Please select an option',
|
||||
VEHICLE_REQUIRED: 'Please select a vehicle',
|
||||
POLICY_NUMBER_REQUIRED: 'Please enter your policy number',
|
||||
POLICY_NUMBER_FORMAT: 'Please enter an alpha-numeric string',
|
||||
PHONE_NUMBER_REQUIRED: 'Please enter phone number',
|
||||
PHONE_NUMBER_FORMAT: 'Please enter your phone number. The format must be ###-###-####',
|
||||
PHONE_NUMBER_FORMAT_SHORT: 'Please enter a valid phone number',
|
||||
POLICY_ZIP_REQUIRED: 'Please enter your policy ZIP',
|
||||
POLICY_ZIP_FORMAT: 'Please enter a valid ZIP',
|
||||
LOSS_CAUSE_REQUIRED: 'Please enter loss cause',
|
||||
LOSS_CITY_REQUIRED: 'Please enter a city',
|
||||
LOSS_CITY_FORMAT: 'Please enter only alpha characters',
|
||||
LOSS_STATE_REQUIRED: 'Please select an option',
|
||||
LOSS_DATE_REQUIRED:
|
||||
'Please select a date. Format must be MM/DD/YYYY and the date must be not in the future',
|
||||
DAMAGE_DATE_REQUIREMENT:
|
||||
'Damage date must be within the past 10 years',
|
||||
DAMAGE_DATE_NO_FUTURE_DATE:
|
||||
'Damage date may not be in the future',
|
||||
DAMAGE_OPTION_REQUIRED: 'Please select an option',
|
||||
|
||||
POLICYHOLDER_FIRST_NAME_REQUIRED: 'Please enter the policyholder first name',
|
||||
|
|
@ -45,7 +56,9 @@ const errorMessages = {
|
|||
YEAR_REQUIRED: 'Please select your vehicle year',
|
||||
MAKE_REQUIRED: 'Please select your vehicle make',
|
||||
MODEL_REQUIRED: 'Please select your vehicle model',
|
||||
STYLE_REQUIRED: 'Please select your vehicle style'
|
||||
};
|
||||
STYLE_REQUIRED: 'Please select your vehicle style',
|
||||
MOBILE_LOCATION_REQUIRED: 'Please enter your service address',
|
||||
DATE_REQUIRED: 'Please select a date'
|
||||
});
|
||||
|
||||
export { errorMessages };
|
||||
export default errorMessages;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
const globalEvents = {
|
||||
const globalEvents = Object.freeze({
|
||||
Categories: {
|
||||
GLOBAL_ALERT: 'GLOBAL_ALERT'
|
||||
},
|
||||
SubCategories: {
|
||||
PAGE_NOT_FOUND: 'PAGE_NOT_FOUND'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const globalEventTypes = {
|
||||
const globalEventTypes = Object.freeze({
|
||||
Success: 'alert-success',
|
||||
Warning: 'alert-warning',
|
||||
Info: 'alert-info',
|
||||
Danger: 'alert-danger'
|
||||
};
|
||||
});
|
||||
|
||||
export { globalEvents, globalEventTypes };
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
const experimentUniverses = {
|
||||
const experimentUniverses = Object.freeze({
|
||||
ISS_FUNNEL: 'ISSFunnel'
|
||||
};
|
||||
});
|
||||
|
||||
const experimentSettings = {
|
||||
const experimentSettings = Object.freeze({
|
||||
GOOGLE_CUSTOM_DIMENSION_INDEX: 'Google Custom Dimension Index'
|
||||
};
|
||||
});
|
||||
|
||||
const experimentTriggers = {
|
||||
const experimentTriggers = Object.freeze({
|
||||
SITE_ENTRY: 'SiteEntry',
|
||||
PAGE_ENTRY: 'PageEntry'
|
||||
};
|
||||
});
|
||||
|
||||
export { experimentUniverses, experimentSettings, experimentTriggers };
|
||||
|
|
|
|||
|
|
@ -1,23 +1,31 @@
|
|||
/**
|
||||
* @file global-rules.js
|
||||
* @module globalRules
|
||||
* @summary Contains all the globally defined rules.
|
||||
* @author MB
|
||||
* @copyright Safelite
|
||||
*/
|
||||
|
||||
/**
|
||||
* @summary Contains all the globally defined rules.
|
||||
*/
|
||||
const globalRules = {
|
||||
const globalRules = Object.freeze({
|
||||
POLICYHOLDER_FIRST_NAME_REQUIRED: 'policyholder-first-name-required',
|
||||
POLICYHOLDER_LAST_NAME_REQUIRED: 'policyholder-last-name-required',
|
||||
POLICY_NUMBER_FORMAT: 'policy-number-format',
|
||||
POLICY_NUMBER_REQUIRED: 'policy-number-required',
|
||||
POLICY_ZIP_FORMAT: 'policy-zip-format',
|
||||
POLICY_ZIP_REQUIRED: 'policy-zip-required',
|
||||
FIRST_NAME_REQUIRED: 'first-name-required',
|
||||
LAST_NAME_REQUIRED: 'last-name-required',
|
||||
DATE_OF_LOSS_CITY_FORMAT: 'loss-city-format',
|
||||
DATE_OF_LOSS_CITY_REQUIRED: 'loss-city-required',
|
||||
DATE_OF_LOSS_STATE_REQUIRED: 'loss-state-required',
|
||||
DATE_OF_LOSS_NOT_FUTURE: 'loss-date-lt-tomorrow',
|
||||
DATE_OF_LOSS_NOT_TEN_YEARS_PAST: 'loss-date-gt-10-years',
|
||||
DATE_OF_LOSS_REQUIRED: 'loss-date-required',
|
||||
EMAIL_ADDRESS_REQUIRED: 'email-required',
|
||||
EMAIL_ADDRESS_FORMAT: 'email-address-format',
|
||||
PHONE_NUMBER_REQUIRED: 'phone-number-required',
|
||||
PHONE_NUMBER_FORMAT: 'phone-number-format',
|
||||
PHONE_NUMBER_FORMAT_SHORT: 'phone-number-format-short',
|
||||
ZIP_CODE_REQUIRED: 'zip-code-required',
|
||||
ZIP_CODE_SEARCH_FORMAT: 'zip-code-search-format',
|
||||
OPTION_REQUIRED: 'option-required'
|
||||
};
|
||||
});
|
||||
|
||||
export default globalRules;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export const headerKeys = {
|
||||
const headerKeys = Object.freeze({
|
||||
EXPERIMENT: 'X-Experiment-Data'
|
||||
};
|
||||
});
|
||||
|
||||
export default headerKeys;
|
||||
|
|
|
|||
15
src/constants/maska-masks.js
Normal file
15
src/constants/maska-masks.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const MaskaFormattedMasks = {
|
||||
PHONE_NUMBER: {
|
||||
mask: 'x##-###-####',
|
||||
tokens: {
|
||||
token: 'x',
|
||||
pattern: '[2-9]'
|
||||
}
|
||||
},
|
||||
ZIPCODE: {
|
||||
mask: '#####',
|
||||
tokens: null
|
||||
}
|
||||
};
|
||||
|
||||
export default MaskaFormattedMasks;
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
// used until real services are available
|
||||
|
||||
const endpoints = {
|
||||
const endpoints = Object.seal({
|
||||
GetRouteInfo: {
|
||||
url: 'https://mockey.qa.sagaws.net/service/ISS/Content/RouteInfo',
|
||||
method: 'GET'
|
||||
}
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
export { endpoints };
|
||||
|
|
|
|||
7
src/constants/package-names.js
Normal file
7
src/constants/package-names.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const packageNames = Object.freeze({
|
||||
TIER_ONE: 'TierOne',
|
||||
TIER_TWO: 'TierTwo',
|
||||
TIER_THREE: 'TierThree'
|
||||
});
|
||||
|
||||
export default packageNames;
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
const partTypeStrings = {
|
||||
const partTypeStrings = Object.freeze({
|
||||
FRONT_WIPER: 'FRONT WIPER',
|
||||
REAR_WIPER: 'REAR WIPER',
|
||||
RAIN_DEFENSE: 'RAIN DEFENSE',
|
||||
RECALIBRATION: 'RECALIBRATION'
|
||||
};
|
||||
RECALIBRATION: 'RECALIBRATION',
|
||||
REPLACE_FEE: 'REPLACE FEE',
|
||||
RECYCLE_FEE: 'RECYCLE FEE',
|
||||
MOBILE_FEE: 'MOBILE FEE',
|
||||
REPAIR_FEE: 'REPAIR FEE'
|
||||
});
|
||||
|
||||
export { partTypeStrings };
|
||||
export default partTypeStrings;
|
||||
|
|
|
|||
15
src/constants/payment-method-constants.js
Normal file
15
src/constants/payment-method-constants.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const hopPaymentMethods = Object.freeze({
|
||||
CREDIT_CARD: 'cc',
|
||||
PAYPAL: 'pp',
|
||||
AFTERPAY: 'ap'
|
||||
});
|
||||
|
||||
const paymentMethods = Object.freeze({
|
||||
NONE: null,
|
||||
PAY_AT_TIME_OF_SERVICE: 'PayAtTimeOfService',
|
||||
CREDIT_CARD: 'CreditCard',
|
||||
PAYPAL: 'Paypal',
|
||||
AFTERPAY: 'Afterpay'
|
||||
});
|
||||
|
||||
export { hopPaymentMethods, paymentMethods };
|
||||
|
|
@ -1,5 +1,22 @@
|
|||
const queryStrings = {
|
||||
ISS_PAGE: 'issPage'
|
||||
};
|
||||
const queryStrings = Object.freeze({
|
||||
ISS_PAGE: 'issPage',
|
||||
AUTH_CODE: 'auth_code',
|
||||
BILL_TO_FIRST_NAME: 'billto_firstname',
|
||||
BILL_TO_LAST_NAME: 'billto_lastname',
|
||||
BILL_TO_POSTAL_CODE: 'billto_postalcode',
|
||||
CARD_EXPIRATION_MONTH: 'card_expirationmonth',
|
||||
CARD_EXPIRATION_YEAR: 'card_expirationyear',
|
||||
CARD_TYPE: 'sgcardtype',
|
||||
DISPLAY_PAY_IN_ADVANCE_ALERT: 'displayPayInAdvanceAlert',
|
||||
ERROR: 'error',
|
||||
TOKEN: 'token',
|
||||
LAST_FOUR: 'last_four',
|
||||
REFERENCE_NUMBER: 'req_reference_number',
|
||||
REFERRAL_SEQ_NUM: 'referralseqnum',
|
||||
SUBSCRIPTIONID: 'subscriptionid',
|
||||
TRANS_REFERENCE_NUMBER: 'auth_trans_ref_no',
|
||||
TRANSACTION_ID: 'transaction_id'
|
||||
|
||||
export { queryStrings };
|
||||
});
|
||||
|
||||
export default queryStrings;
|
||||
|
|
|
|||
24
src/constants/schedule-constants.js
Normal file
24
src/constants/schedule-constants.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const AppointmentTypeStrings = {
|
||||
IN_SHOP: 'Inshop',
|
||||
MOBILE: 'Mobile',
|
||||
MOBILE_NOT_ITAC_AND_NOT_NOCOMP: 'Mobile-Not-ITAC-And-Not-NoComp',
|
||||
DROP_OFF: 'Dropoff'
|
||||
};
|
||||
const PREMIUM_FEE_PART_TYPE = 'EARLY BIRD';
|
||||
const PREMIUM_TIME_SLOT_ID_FLAG = '-PREMIUM';
|
||||
const RouteCodeFlags = {
|
||||
ALL_DAY_DROP_OFF: 'ALL DAY DROP OFF',
|
||||
OVERNIGHT_DROP_OFF: 'OVERNIGHT DROP OFF'
|
||||
};
|
||||
|
||||
const GET_SHOP_TIME_SLOTS = 'getShopTimeSlots';
|
||||
const GET_MOBILE_TIME_SLOTS = 'getMobileTimeSlots';
|
||||
|
||||
export {
|
||||
AppointmentTypeStrings,
|
||||
PREMIUM_TIME_SLOT_ID_FLAG,
|
||||
PREMIUM_FEE_PART_TYPE,
|
||||
RouteCodeFlags,
|
||||
GET_SHOP_TIME_SLOTS,
|
||||
GET_MOBILE_TIME_SLOTS
|
||||
};
|
||||
6
src/constants/service-type.js
Normal file
6
src/constants/service-type.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
const serviceType = Object.freeze({
|
||||
REPLACEMENT: 'replacement',
|
||||
REPAIR: 'repair',
|
||||
REPLACEMENT_AND_RECALIBRATION: 'replacement and recalibration'
|
||||
});
|
||||
export default serviceType;
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
export const states = {
|
||||
/**
|
||||
* @module states
|
||||
* @author T-Wrecks Team
|
||||
* @copyright Safelite
|
||||
*/
|
||||
const states = Object.freeze({
|
||||
AL: 'Alabama',
|
||||
AK: 'Alaska',
|
||||
AZ: 'Arizona',
|
||||
|
|
@ -50,4 +55,6 @@ export const states = {
|
|||
WV: 'West Virginia',
|
||||
WI: 'Wisconsin',
|
||||
WY: 'Wyoming'
|
||||
};
|
||||
});
|
||||
|
||||
export default states;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// TintMap with array keys by location, lowercase to avoid as much string mismatching as possible.
|
||||
// Src assumes you have a @/assets/img/tints/, making the final value @/assets/img/tints/{Src} in the markup.
|
||||
// See vehicle-parts for implementation example.
|
||||
const tintMap = {
|
||||
const tintMap = Object.freeze({
|
||||
other: [
|
||||
// Blue Shade
|
||||
{ name: 'blue tint, blue shade', src: 'Glass-BlueShade-BlueTint.svg' },
|
||||
|
|
@ -71,23 +71,19 @@ const tintMap = {
|
|||
// No shade or tint
|
||||
{ name: 'clear', src: 'Windshield-NoShade-NoTint.svg' }
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
// Gets the tint image source string given the glass location, and the tint description (like 'Green Tint')
|
||||
// Use lowered strings here to try to avoid mismatch. Returns an empty string if array key doesn't exist.
|
||||
// Returns undefined if no items are found.
|
||||
export function getTintImage(glassLocation, colorString) {
|
||||
const getTintImage = (glassLocation, colorString) => {
|
||||
// Windshield glass has special images, all other glass uses the same though.
|
||||
if (glassLocation.toLowerCase() !== 'windshield') {
|
||||
glassLocation = 'other';
|
||||
}
|
||||
|
||||
if (tintMap[glassLocation.toLowerCase()] === undefined) {
|
||||
const glassLoc = (glassLocation.toLowerCase() !== 'windshield') ? 'other' : 'windshield';
|
||||
if (tintMap[glassLoc] === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const tintImageSource = tintMap[glassLocation.toLowerCase()]
|
||||
.find((item) => item.name.toLowerCase() === colorString.toLowerCase());
|
||||
return tintMap[glassLoc].find((item) => item.name.toLowerCase() === colorString.toLowerCase());
|
||||
};
|
||||
|
||||
return tintImageSource;
|
||||
}
|
||||
export default getTintImage;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const vehicleCategories = {
|
||||
const vehicleCategories = Object.freeze({
|
||||
CAR: 'CAR',
|
||||
TRUCK: 'TRUCK',
|
||||
VAN: 'VAN',
|
||||
|
|
@ -6,6 +6,6 @@ const vehicleCategories = {
|
|||
SUV: 'SUV',
|
||||
MOTORHOME: 'MOTOR HOME',
|
||||
SEMI: 'SEMI'
|
||||
};
|
||||
});
|
||||
|
||||
export { vehicleCategories };
|
||||
export default vehicleCategories;
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ const vehicleSelectionOptions = Object.freeze({
|
|||
VEHICLE_NOT_LISTED: 'Vehicle not listed'
|
||||
});
|
||||
|
||||
export { vehicleSelectionOptions };
|
||||
export default vehicleSelectionOptions;
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ const vinLookupMethodSelections = Object.freeze({
|
|||
HOMEADDRESS: 'HomeAddress'
|
||||
});
|
||||
|
||||
export { vinLookupMethodSelections };
|
||||
export default vinLookupMethodSelections;
|
||||
|
|
|
|||
5
src/constants/web-storage-constants.js
Normal file
5
src/constants/web-storage-constants.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
const webStorageConstants = Object.freeze({
|
||||
SUBMITTED_ORDER: 'submittedOrder'
|
||||
});
|
||||
|
||||
export default webStorageConstants;
|
||||
|
|
@ -30,7 +30,7 @@ import {
|
|||
handleButtonComponentFocus,
|
||||
handleInputComponentBlur
|
||||
} from '@/helpers/button-question-focus-helper';
|
||||
import { inputButtonProps } from '@/digital-components/base-input-button/button-functionality-props';
|
||||
import inputButtonProps from '@/digital-components/base-input-button/button-functionality-props';
|
||||
|
||||
export default {
|
||||
name: 'base-input-button',
|
||||
|
|
@ -49,10 +49,12 @@ export default {
|
|||
validateOnMount: false
|
||||
};
|
||||
|
||||
const { handleChange, meta, errors }
|
||||
= useField(toRef(props, 'groupName'),
|
||||
const { handleChange, meta, errors } =
|
||||
useField(
|
||||
toRef(props, 'groupName'),
|
||||
toRef(props, 'validationRules'),
|
||||
fieldOptions);
|
||||
fieldOptions
|
||||
);
|
||||
|
||||
return {
|
||||
handleChange,
|
||||
|
|
@ -72,7 +74,8 @@ export default {
|
|||
return this.modelValue.includes(this.value);
|
||||
}
|
||||
if (!this.isMultiSelect) {
|
||||
return this.modelValue === this.value;
|
||||
// eslint-disable-next-line eqeqeq
|
||||
return this.modelValue == this.value;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
|
@ -103,25 +106,25 @@ export default {
|
|||
handleEventAction(eventType, e) {
|
||||
if (this.isMultiSelect) {
|
||||
switch (eventType) {
|
||||
case this.eventTypes.CHANGE:
|
||||
this.handleClick(e);
|
||||
break;
|
||||
default:
|
||||
case this.eventTypes.CHANGE:
|
||||
this.handleClick(e);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
switch (eventType) {
|
||||
case this.eventTypes.CLICK:
|
||||
case this.eventTypes.ENTER:
|
||||
case this.eventTypes.SPACE:
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case this.eventTypes.CHANGE:
|
||||
case this.eventTypes.CLICK:
|
||||
case this.eventTypes.ENTER:
|
||||
case this.eventTypes.SPACE:
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case this.eventTypes.CHANGE:
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
this.selectingInitiatesLoad
|
||||
? this.handleSelectionChange(e)
|
||||
: this.handleClick(e);
|
||||
break;
|
||||
default:
|
||||
this.selectingInitiatesLoad
|
||||
? this.handleSelectionChange(e)
|
||||
: this.handleClick(e);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export const inputButtonProps = {
|
||||
const inputButtonProps = Object.seal({
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
|
|
@ -35,4 +35,6 @@ export const inputButtonProps = {
|
|||
default: false
|
||||
},
|
||||
suppressError: Boolean
|
||||
};
|
||||
});
|
||||
|
||||
export default inputButtonProps;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,18 @@
|
|||
/* eslint-disable max-len */
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import buttonQuestion from '@/digital-components/button-question/button-question';
|
||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||
|
||||
/** @ignore */
|
||||
function setupMocks(mountOptionsMockData = {}) {
|
||||
const defaultMountOptions = { route: { query: { issPage: 'page-name' } } };
|
||||
const baseMountOptions =
|
||||
getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData));
|
||||
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
|
||||
|
||||
return allMountOptions;
|
||||
}
|
||||
|
||||
describe('buttonQuestion.vue', () => {
|
||||
it('Fieldset classes should contain row if button type is listCard', () => {
|
||||
// Act
|
||||
|
|
@ -51,8 +62,10 @@ describe('buttonQuestion.vue', () => {
|
|||
describe('selectedValues', () => {
|
||||
test('is radio => should emit captured value', async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
setupMocks({ propsData: { groupName: 'group-name' } }));
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({ propsData: { groupName: 'group-name' } })
|
||||
);
|
||||
await wrapper.setProps({
|
||||
answers: ['2022', '2021', '2020'],
|
||||
isMultiSelect: false,
|
||||
|
|
@ -67,8 +80,10 @@ describe('buttonQuestion.vue', () => {
|
|||
});
|
||||
|
||||
test('is checkbox => should emit captured value', async () => {
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
setupMocks({ propsData: { groupName: 'group-name' } }));
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({ propsData: { groupName: 'group-name' } })
|
||||
);
|
||||
await wrapper.setProps({
|
||||
answers: ['2022', '2021', '2020'],
|
||||
isMultiSelect: false,
|
||||
|
|
@ -93,7 +108,8 @@ describe('buttonQuestion.vue', () => {
|
|||
describe('buttonLabel', () => {
|
||||
test('answers have buttonLabel properties => buttonsInfo buttonsLabel properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -105,7 +121,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -117,7 +134,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have Text properties, no buttonLabel properties => buttonsInfo buttonsLabel properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -129,7 +147,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -141,7 +160,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have buttonLabel and Text properties => buttonsInfo buttonsLabel properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -155,7 +175,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -167,12 +188,14 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers is an array of strings => buttonLabel is answer values', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: ['answer 1', 'answer 2']
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -186,7 +209,8 @@ describe('buttonQuestion.vue', () => {
|
|||
describe('altText', () => {
|
||||
test('answers have altText properties => buttonsInfo altText properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -198,7 +222,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -210,7 +235,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have Name properties, no buttonLabel properties => buttonsInfo altText properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -222,7 +248,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -234,7 +261,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have altText and Name properties => buttonsInfo altText properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -248,7 +276,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -260,12 +289,14 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers is an array of strings => altText is answer values', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: ['answer 1', 'answer 2']
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -279,7 +310,8 @@ describe('buttonQuestion.vue', () => {
|
|||
describe('buttonLabelSubCopy', () => {
|
||||
test('answers have buttonLabelSubCopy properties => buttonsInfo buttonLabelSubCopy properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -291,7 +323,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -303,7 +336,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have SubText properties, no buttonLabelSubCopy properties => buttonsInfo buttonLabelSubCopy properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -315,7 +349,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -327,7 +362,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have buttonLabelSubCopy and SubText properties => buttonsInfo buttonLabelSubCopy properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -341,7 +377,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -353,12 +390,14 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers is an array of strings => there are no buttonLabelSubCopy properties', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: ['answer 1', 'answer 2']
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -372,7 +411,8 @@ describe('buttonQuestion.vue', () => {
|
|||
describe('buttonImage', () => {
|
||||
test('answers have buttonImage properties => buttonsInfo buttonImage properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -384,7 +424,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -396,7 +437,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have AnswerImageUrl properties, no buttonImage properties => buttonsInfo buttonImage properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -408,7 +450,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -420,7 +463,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have buttonImage and AnswerImageUrl properties => buttonsInfo buttonImage properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -434,7 +478,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -446,12 +491,14 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers is an array of strings => there are no buttonImage properties', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: ['answer 1', 'answer 2']
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -465,7 +512,8 @@ describe('buttonQuestion.vue', () => {
|
|||
describe('buttonImageId', () => {
|
||||
test('answers have buttonImageId properties => buttonsInfo buttonImageId properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -477,7 +525,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -489,7 +538,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have ImageId properties, no buttonImageId properties => buttonsInfo buttonImage properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -501,7 +551,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -513,7 +564,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have buttonImageId and ImageId properties => buttonsInfo buttonImageId properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: [
|
||||
|
|
@ -527,7 +579,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -539,12 +592,14 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers is an array of strings => there are no buttonImageId properties', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: ['answer 1', 'answer 2']
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -557,16 +612,19 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
describe('groupName', () => {
|
||||
const answers = [[['answer 1', 'answer 2']], [[{ value: 1 }, { value: 2 }]]];
|
||||
test.each(answers)('answers have groupName properties with spaces => buttonsInfo groupName properties are correct',
|
||||
test.each(answers)(
|
||||
'answers have groupName properties with spaces => buttonsInfo groupName properties are correct',
|
||||
(answerGroup) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: answerGroup,
|
||||
groupName: 'this is my group name'
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -574,18 +632,22 @@ describe('buttonQuestion.vue', () => {
|
|||
// Assert
|
||||
expect(buttonsInfo[0].groupName).toEqual('this-is-my-group-name');
|
||||
expect(buttonsInfo[1].groupName).toEqual('this-is-my-group-name');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
test.each(answers)('answers have groupName properties with no spaces => buttonsInfo groupName properties are correct',
|
||||
test.each(answers)(
|
||||
'answers have groupName properties with no spaces => buttonsInfo groupName properties are correct',
|
||||
(answerGroup) => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
answers: answerGroup,
|
||||
groupName: 'this-is-my-group-name'
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -593,14 +655,16 @@ describe('buttonQuestion.vue', () => {
|
|||
// Assert
|
||||
expect(buttonsInfo[0].groupName).toEqual('this-is-my-group-name');
|
||||
expect(buttonsInfo[1].groupName).toEqual('this-is-my-group-name');
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('value', () => {
|
||||
describe('useTextForValue is true', () => {
|
||||
test('answers have value properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: true,
|
||||
|
|
@ -613,7 +677,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -625,7 +690,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have Text properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: true,
|
||||
|
|
@ -638,7 +704,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -650,7 +717,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have Name properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: true,
|
||||
|
|
@ -663,7 +731,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -675,7 +744,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have value and Text properties, no Name properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: true,
|
||||
|
|
@ -690,7 +760,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -702,7 +773,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have value and Name properties, no Text properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: true,
|
||||
|
|
@ -717,7 +789,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -729,7 +802,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have Text and Name properties, no value properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: true,
|
||||
|
|
@ -744,7 +818,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -756,7 +831,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have value, Text, and Name properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: true,
|
||||
|
|
@ -773,7 +849,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -785,13 +862,15 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers is an array of strings => buttonInfo value property values are values from array', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: true,
|
||||
answers: ['answer 1', 'answer 2']
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -805,7 +884,8 @@ describe('buttonQuestion.vue', () => {
|
|||
describe('useTextForValue is false', () => {
|
||||
test('answers have value properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: false,
|
||||
|
|
@ -818,7 +898,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -830,7 +911,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have Text properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: false,
|
||||
|
|
@ -843,7 +925,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -855,7 +938,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have Name properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: false,
|
||||
|
|
@ -868,7 +952,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -880,7 +965,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have value and Text properties, no Name properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: false,
|
||||
|
|
@ -895,7 +981,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -907,7 +994,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have value and Name properties, no Text properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: false,
|
||||
|
|
@ -922,7 +1010,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -934,7 +1023,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have Text and Name properties, no value properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: false,
|
||||
|
|
@ -949,7 +1039,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -961,7 +1052,8 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers have value, Text, and Name properties => buttonsInfo value properties are correct', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: false,
|
||||
|
|
@ -978,7 +1070,8 @@ describe('buttonQuestion.vue', () => {
|
|||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -990,13 +1083,15 @@ describe('buttonQuestion.vue', () => {
|
|||
|
||||
test('answers is an array of strings => buttonInfo value property values are values from array', () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(buttonQuestion,
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
useTextForValue: false,
|
||||
answers: ['answer 1', 'answer 2']
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
const { buttonsInfo } = wrapper.vm;
|
||||
|
|
@ -1009,12 +1104,3 @@ describe('buttonQuestion.vue', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks(mountOptionsMockData = {}) {
|
||||
const defaultMountOptions = { route: { query: { issPage: 'page-name' } } };
|
||||
const baseMountOptions
|
||||
= getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData));
|
||||
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
|
||||
|
||||
return allMountOptions;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
:isWide="isWide"
|
||||
:validationRules="validationRules"
|
||||
:textPosition="textPosition"
|
||||
:additionalButtonData="additionalButtonData"
|
||||
:additionalButtonStyling="additionalButtonStyling"
|
||||
:lastValuePushedToGa="lastValuePushedToGa"
|
||||
:setLastValuePushedToGa="setLastValuePushedToGa"
|
||||
|
|
@ -76,12 +77,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import listButton from '@/ux-components/list-button/list-button';
|
||||
import listButtonHorizontal from '@/ux-components/list-button-horizontal/list-button-horizontal';
|
||||
import listCard from '@/ux-components/list-card/list-card';
|
||||
import radio from '@/ux-components/radio/radio';
|
||||
import { ErrorMessage } from 'vee-validate';
|
||||
import providerPrefRadio from '@/layouts/provider-preference/provider-pref-radio/provider-pref-radio';
|
||||
import listButton from '@/ux-components/list-button/list-button.vue';
|
||||
import listButtonHorizontal from '@/ux-components/list-button-horizontal/list-button-horizontal.vue';
|
||||
import listCard from '@/ux-components/list-card/list-card.vue';
|
||||
import radio from '@/ux-components/radio/radio.vue';
|
||||
import { useField, ErrorMessage } from 'vee-validate';
|
||||
import providerPrefRadio from '@/layouts/provider-preference/provider-pref-radio/provider-pref-radio.vue';
|
||||
|
||||
export default {
|
||||
name: 'button-question',
|
||||
|
|
@ -129,11 +130,34 @@ export default {
|
|||
suppressError: Boolean,
|
||||
useTextForValue: Boolean,
|
||||
valueToLogType: String,
|
||||
additionalButtonData: Object,
|
||||
additionalButtonStyling: String,
|
||||
isSmallQuestionText: Boolean,
|
||||
isSmallQuestionLabelText: Boolean
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props) {
|
||||
const propsClone = { ...props };
|
||||
const { modelValue } = propsClone;
|
||||
|
||||
const fieldOptions = {
|
||||
value: modelValue,
|
||||
initialValue: null
|
||||
};
|
||||
|
||||
const { errorMessage, handleBlur, handleChange, meta, validate, errors, resetField } =
|
||||
useField(props.groupName, props.validationRules, fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
validate,
|
||||
meta,
|
||||
errors,
|
||||
resetField
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lastValuePushedToGa: null
|
||||
|
|
@ -160,28 +184,28 @@ export default {
|
|||
getComponentLoopWrapperClasses() {
|
||||
let classes;
|
||||
switch (this.buttonTypeString) {
|
||||
case 'listButton':
|
||||
classes = 'w-100';
|
||||
break;
|
||||
case 'listButtonHorizontal':
|
||||
classes = 'd-flex flex-row p-0';
|
||||
break;
|
||||
case 'listCard':
|
||||
classes = 'row g-2 justify-content-center';
|
||||
if (this.isWide) {
|
||||
classes += ' flex-column';
|
||||
}
|
||||
break;
|
||||
case 'radio':
|
||||
classes = 'ui-radio d-flex';
|
||||
break;
|
||||
case 'servicePackageRadio':
|
||||
classes = 'package-main';
|
||||
break;
|
||||
case 'providerPrefRadio':
|
||||
classes = 'option-main';
|
||||
break;
|
||||
default:
|
||||
case 'listButton':
|
||||
classes = 'w-100';
|
||||
break;
|
||||
case 'listButtonHorizontal':
|
||||
classes = 'd-flex flex-row p-0';
|
||||
break;
|
||||
case 'listCard':
|
||||
classes = 'row g-2 justify-content-center';
|
||||
if (this.isWide) {
|
||||
classes += ' flex-column';
|
||||
}
|
||||
break;
|
||||
case 'radio':
|
||||
classes = 'ui-radio d-flex';
|
||||
break;
|
||||
case 'servicePackageRadio':
|
||||
classes = 'package-main';
|
||||
break;
|
||||
case 'providerPrefRadio':
|
||||
classes = 'option-main';
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return classes;
|
||||
},
|
||||
|
|
@ -191,16 +215,16 @@ export default {
|
|||
classes += this.isWide ? 'col-12' : 'col';
|
||||
|
||||
switch (this.buttonTypeString) {
|
||||
case 'radio':
|
||||
classes += ' radio-button-container';
|
||||
break;
|
||||
case 'servicePackageRadio':
|
||||
classes = 'package-wrapper';
|
||||
break;
|
||||
case 'providerPrefRadio':
|
||||
classes = 'option-wrapper';
|
||||
break;
|
||||
default:
|
||||
case 'radio':
|
||||
classes += ' radio-button-container';
|
||||
break;
|
||||
case 'servicePackageRadio':
|
||||
classes = 'package-wrapper';
|
||||
break;
|
||||
case 'providerPrefRadio':
|
||||
classes = 'option-wrapper';
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return classes;
|
||||
|
|
@ -231,11 +255,23 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(newValue) {
|
||||
this.resetField({
|
||||
value: newValue
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
if (this.buttonTypeObject) {
|
||||
this.$options.components[this.buttonTypeString] = this.buttonTypeObject;
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.resetField({
|
||||
value: ''
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
formatString(str) {
|
||||
return String(str).replaceAll(' ', '-');
|
||||
|
|
|
|||
1064
src/digital-components/date-picker/date-picker.vue
Normal file
1064
src/digital-components/date-picker/date-picker.vue
Normal file
File diff suppressed because it is too large
Load diff
26
src/digital-components/date-picker/mixins/constants.js
Normal file
26
src/digital-components/date-picker/mixins/constants.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
const TIMINGFUNC_MAP = {
|
||||
linear: (t) => t,
|
||||
'ease-in': (t) => t * t,
|
||||
'ease-out': (t) => t * (2 - t),
|
||||
'ease-in-out': (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t)
|
||||
};
|
||||
const BUFFER_OFFSET = 10;
|
||||
|
||||
const MONTHS_OF_YEAR = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
];
|
||||
|
||||
const DAYS_OF_WEEK = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
|
||||
export { TIMINGFUNC_MAP, BUFFER_OFFSET, MONTHS_OF_YEAR, DAYS_OF_WEEK };
|
||||
10
src/digital-components/date-picker/mixins/helpers.js
Normal file
10
src/digital-components/date-picker/mixins/helpers.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
const selectableDaysOptions = Object.freeze({
|
||||
CUSTOM: 'custom',
|
||||
PAST: 'past'
|
||||
});
|
||||
|
||||
const requiredParameter = () => {
|
||||
throw new Error('parameter is required');
|
||||
};
|
||||
|
||||
export { selectableDaysOptions, requiredParameter };
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import dropdownQuestion from './dropdown-question';
|
||||
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
|
||||
|
||||
// Mock CMS content
|
||||
const questionText = 'Question Text';
|
||||
|
|
@ -46,6 +46,7 @@ describe('dropdownQuestion.vue', () => {
|
|||
expect(label.text()).toContain(questionText);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
it("Should render the 'questionText' data value with '⁠' after the first character of each word in the label text when disableAutoFill is true.", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(dropdownQuestion, {
|
||||
|
|
@ -154,6 +155,6 @@ describe('dropdownQuestion.vue', () => {
|
|||
wrapper.vm.$options.watch.selectedOption.call(wrapper.vm, 1);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.handleChange).toHaveBeenCalled;
|
||||
expect(wrapper.vm.handleChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="dropdown-question"
|
||||
:class="(errors && errors.length) || hasError ? 'has-error' : ''">
|
||||
:class="(meta.touched && errors && errors.length) || hasError ? 'has-error' : ''">
|
||||
<label
|
||||
:for="inputId"
|
||||
:aria-label="questionText"
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
v-if="placeHolderText"
|
||||
value=""
|
||||
selected>
|
||||
{{ placeHolderText }}
|
||||
{{ placeHolderText }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(value, name, index) in options"
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
</option>
|
||||
</select>
|
||||
<div
|
||||
v-show="errorMessage"
|
||||
v-show="meta.touched && errorMessage && !isDisabled"
|
||||
class="row mt-1 form-test-error">
|
||||
<span role="alert">{{ errorMessage }}</span>
|
||||
</div>
|
||||
|
|
@ -65,12 +65,12 @@ export default {
|
|||
let initialValue;
|
||||
|
||||
switch (typeof modelValue) {
|
||||
case 'number':
|
||||
initialValue = modelValue;
|
||||
break;
|
||||
default:
|
||||
initialValue = (modelValue && modelValue.length > 0) ? modelValue : '';
|
||||
break;
|
||||
case 'number':
|
||||
initialValue = modelValue;
|
||||
break;
|
||||
default:
|
||||
initialValue = modelValue && modelValue.length > 0 ? modelValue : '';
|
||||
break;
|
||||
}
|
||||
|
||||
const fieldOptions = {
|
||||
|
|
@ -79,16 +79,21 @@ export default {
|
|||
initialValue
|
||||
};
|
||||
|
||||
const { errorMessage, handleBlur, handleChange, meta, errors } = useField(props.inputId,
|
||||
props.validationRules,
|
||||
fieldOptions);
|
||||
const { errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
setTouched,
|
||||
meta, errors,
|
||||
setErrors } = useField(props.inputId, props.validationRules, fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
meta,
|
||||
errors
|
||||
errors,
|
||||
setTouched,
|
||||
setErrors
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -111,12 +116,8 @@ export default {
|
|||
const words = this.questionText.toString().split(/[ ]+/);
|
||||
words.forEach((word) => {
|
||||
const position = 1;
|
||||
word = [
|
||||
word.toString().slice(0, position),
|
||||
noBreakChar,
|
||||
word.toString().slice(position)
|
||||
].join('');
|
||||
questionText += `${word} `;
|
||||
const newWord = [word.toString().slice(0, position), noBreakChar, word.toString().slice(position)].join('');
|
||||
questionText += `${newWord} `;
|
||||
});
|
||||
|
||||
questionText = questionText.trimEnd();
|
||||
|
|
@ -129,6 +130,11 @@ export default {
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
isDisabled(newValue, oldValue) {
|
||||
if (newValue !== oldValue) {
|
||||
this.setTouched(false, null);
|
||||
}
|
||||
},
|
||||
selectedOption(newValue) {
|
||||
this.handleChange(newValue);
|
||||
}
|
||||
|
|
@ -146,9 +152,9 @@ export default {
|
|||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.form-select {
|
||||
color: $gray-600;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
border: 1px solid $gray-500;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
color: $gray-600;
|
||||
border-radius: 0.5rem;
|
||||
min-height: 3rem;
|
||||
&:focus,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
|
|||
import crypto from 'crypto';
|
||||
import { useForm } from 'vee-validate';
|
||||
import { Modal } from 'bootstrap';
|
||||
import modal from './modal';
|
||||
import modal from '@/digital-components/modal/modal.vue';
|
||||
|
||||
jest.mock('vee-validate', () => ({
|
||||
useForm: jest.fn()
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
loaderColor="white"
|
||||
:buttonText="footerButtonText"
|
||||
:class="(isButtonDisabled || isFooterButtonDisabled) && 'form-test-invalid'"
|
||||
@click-event="validateAndEmit" />
|
||||
@clickEvent="validateAndEmit" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -42,11 +42,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import modalButtonMain from '@/ux-components/modal-button-main/modal-button-main';
|
||||
import modalButtonMain from '@/ux-components/modal-button-main/modal-button-main.vue';
|
||||
import { Modal } from 'bootstrap';
|
||||
import { useForm } from 'vee-validate';
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
name: 'modal',
|
||||
components: {
|
||||
modalButtonMain
|
||||
|
|
@ -63,12 +64,13 @@ export default {
|
|||
},
|
||||
isButtonDisabled: Boolean
|
||||
},
|
||||
emits: ['footer-button-event'],
|
||||
emits: ['footer-button-event', 'isModalOpened'],
|
||||
setup(props) {
|
||||
const modalId = props.modalId ? props.modalId : `modal-${crypto.randomUUID()}`;
|
||||
|
||||
const { meta, validate, resetForm } = useForm();
|
||||
|
||||
// TODO: Correct Duplicate key 'modalId' issue.
|
||||
return {
|
||||
modalId,
|
||||
meta,
|
||||
|
|
@ -106,10 +108,12 @@ export default {
|
|||
openModal() {
|
||||
const modal = Modal.getOrCreateInstance(document.getElementById(this.modalId));
|
||||
modal?.show();
|
||||
this.$emit('isModalOpened', true);
|
||||
},
|
||||
closeModal() {
|
||||
const modal = Modal.getInstance(document.getElementById(this.modalId));
|
||||
modal?.hide();
|
||||
this.$emit('isModalOpened', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -117,11 +121,19 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.modal {
|
||||
overflow: hidden;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
h5,
|
||||
strong,
|
||||
|
||||
:deep(h5) {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
:deep(p) {
|
||||
strong {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.subheader-text {
|
||||
color: $black;
|
||||
}
|
||||
|
|
@ -140,6 +152,13 @@ export default {
|
|||
color: $black;
|
||||
}
|
||||
}
|
||||
.modal-footer {
|
||||
position: sticky;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
background-color: $gray-100;
|
||||
}
|
||||
&.modal-component {
|
||||
.modal-dialog {
|
||||
max-width: 576px;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,42 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import questionChain from '@/digital-components/question-chain/question-chain';
|
||||
import questionChain from '@/digital-components/question-chain/question-chain.vue';
|
||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
/** @ignore */
|
||||
function setupMocks({
|
||||
questionDataProp = [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: 'Does only your center sliding piece need to be replaced?',
|
||||
answers: [
|
||||
{
|
||||
answerResult: 'DB09410',
|
||||
answerText: 'Yes',
|
||||
nextQuestionSequence: null
|
||||
},
|
||||
{
|
||||
answerResult: 'DB10840',
|
||||
answerText: 'No',
|
||||
nextQuestionSequence: null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}) {
|
||||
Element.prototype.scrollIntoView = jest.fn();
|
||||
|
||||
const mountOptions = getMountOptions({});
|
||||
mountOptions.propsData = {
|
||||
questionData: questionDataProp
|
||||
};
|
||||
mountOptions.attachTo = document.body;
|
||||
|
||||
const wrapper = shallowMount(questionChain, mountOptions);
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
||||
describe('Question Chain component', () => {
|
||||
describe('on create...', () => {
|
||||
test('Should populate questions data array', async () => {
|
||||
|
|
@ -205,7 +239,7 @@ describe('Question Chain component', () => {
|
|||
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.questions[1].answerSelected).toBeUndefined;
|
||||
expect(wrapper.vm.questions[1].answerSelected).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should return false if returnedAnswer is a nextQuestion (not a final answer)', async () => {
|
||||
|
|
@ -279,36 +313,3 @@ describe('Question Chain component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({
|
||||
questionDataProp = [
|
||||
{
|
||||
questionSequence: 1,
|
||||
questionText: 'Does only your center sliding piece need to be replaced?',
|
||||
answers: [
|
||||
{
|
||||
answerResult: 'DB09410',
|
||||
answerText: 'Yes',
|
||||
nextQuestionSequence: null
|
||||
},
|
||||
{
|
||||
answerResult: 'DB10840',
|
||||
answerText: 'No',
|
||||
nextQuestionSequence: null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}) {
|
||||
Element.prototype.scrollIntoView = jest.fn();
|
||||
|
||||
const mountOptions = getMountOptions({});
|
||||
mountOptions.propsData = {
|
||||
questionData: questionDataProp
|
||||
};
|
||||
mountOptions.attachTo = document.body;
|
||||
|
||||
const wrapper = shallowMount(questionChain, mountOptions);
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import buttonQuestion from '@/digital-components/button-question/button-question';
|
||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||
import { useValidateForm } from 'vee-validate';
|
||||
|
||||
export default {
|
||||
|
|
@ -97,6 +97,7 @@ export default {
|
|||
"1|answer|DD11132|Yes"
|
||||
*/
|
||||
|
||||
// TODO: Assignment to parm
|
||||
question.answerSelected = returnedAnswer;
|
||||
const isQuestionChainComplete = this.getQuestionChainAnswerIfComplete(returnedAnswer);
|
||||
|
||||
|
|
@ -121,6 +122,7 @@ export default {
|
|||
const questionAnswerText = returnedAnswerArray[3];
|
||||
const answeredQuestions = [];
|
||||
|
||||
// TODO: This forEach could probably be converted into something more reactive
|
||||
this.questions.forEach((q) => {
|
||||
// find this question and mark it as "answered" by populating answerSelected
|
||||
if (q.questionSequence === questionNum) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import TextBlock from './text-block';
|
||||
import TextBlock from '@/digital-components/text-block/text-block.vue';
|
||||
|
||||
const mockCmsContent = {
|
||||
Text: 'Sample text here.'
|
||||
|
|
@ -17,6 +17,16 @@ const mockProps = {
|
|||
justifyText: 'mockJustifyText'
|
||||
};
|
||||
|
||||
// Mock fetchCmsContentForPage
|
||||
jest.mock('@/helpers/cms-content-helper', () => ({
|
||||
doesCopyContainRouterLink: jest.fn(),
|
||||
doesCopyContainTextLink: jest.fn(),
|
||||
splitCopyOnCMSPlaceHolder: jest.fn(() => mockCmsContent.Text.split(/{(.*?)}/g)),
|
||||
getRouterLinkRouteFromCopy: jest.fn(),
|
||||
getRouterLinkDisplayTextFromCopy: jest.fn(),
|
||||
getExternalLink: jest.fn()
|
||||
}));
|
||||
|
||||
describe('modal.vue', () => {
|
||||
it("Should display 'Text' when 'Text' is defined in the CMS", async () => {
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -1,23 +1,90 @@
|
|||
<template>
|
||||
<div
|
||||
class="text-block w-100"
|
||||
:class="[justifyText, typeStyle, fontWeight]"
|
||||
v-html="this.TextBlockCopy"></div>
|
||||
:class="[justifyText, typeStyle, fontWeight, marginTopClass]">
|
||||
<span
|
||||
v-for="copy in splitCopyOnCMSPlaceHolder(textBlockCopy)"
|
||||
:key="copy">
|
||||
<span v-if="doesCopyContainRouterLink(copy)">
|
||||
<routerLink
|
||||
:to="{
|
||||
query: { issPage: `${getRouterLinkRouteFromCopy(copy)}` },
|
||||
name: 'root',
|
||||
}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</routerLink>
|
||||
</span>
|
||||
<span v-else-if="doesCopyContainTextLink(copy)">
|
||||
<textLink
|
||||
linkType="text"
|
||||
:text="getRouterLinkDisplayTextFromCopy(copy)"
|
||||
:href="getExternalLink(copy)"
|
||||
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
|
||||
@click-event="$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))" />
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
v-html="copy"></span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Components
|
||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||
|
||||
// Supporting files
|
||||
import {
|
||||
doesCopyContainRouterLink,
|
||||
doesCopyContainTextLink,
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
getRouterLinkRouteFromCopy,
|
||||
getRouterLinkDisplayTextFromCopy,
|
||||
getExternalLink
|
||||
} from '@/helpers/cms-content-helper';
|
||||
import applicationConfig from '@/constants/application-config';
|
||||
|
||||
export default {
|
||||
name: 'textBlock',
|
||||
name: 'text-block',
|
||||
components: {
|
||||
textLink
|
||||
},
|
||||
props: {
|
||||
customText: String, // used to allow the insert of token values into textblock
|
||||
justifyText: String, // left, right, center
|
||||
typeStyle: String, // h1-h6, body, small, label, caption
|
||||
// (see Figma or Confluence documentation)
|
||||
fontWeight: String, // bold=500, default is 400
|
||||
cmsWidgetName: String
|
||||
cmsWidgetName: String,
|
||||
marginTopSizeOverride: Number // override mt-2 with a bootstrap size from 0-5 or auto
|
||||
},
|
||||
computed: {
|
||||
TextBlockCopy() {
|
||||
pageQueryString() {
|
||||
return applicationConfig.PAGE_QUERYSTRING;
|
||||
},
|
||||
textBlockCopy() {
|
||||
if (this.customText) {
|
||||
return this.customText;
|
||||
}
|
||||
return this.getCmsContent(this.cmsWidgetName, 'Text');
|
||||
},
|
||||
marginTopClass() {
|
||||
if (this.marginTopSizeOverride === 'auto') {
|
||||
return 'mt-auto';
|
||||
}
|
||||
if (this.marginTopSizeOverride >= 0 && this.marginTopSizeOverride <= 5) {
|
||||
return `mt-${this.marginTopSizeOverride}`;
|
||||
}
|
||||
return 'mt-2';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doesCopyContainRouterLink,
|
||||
doesCopyContainTextLink,
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
getRouterLinkRouteFromCopy,
|
||||
getRouterLinkDisplayTextFromCopy,
|
||||
getExternalLink,
|
||||
navigateWithScenario(scenarioName) {
|
||||
this.$router.navigateWithoutSaving(scenarioName, this.$route);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -30,15 +97,16 @@ export default {
|
|||
justify-content: flex-start;
|
||||
}
|
||||
&.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
&.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
&.bold {
|
||||
font-weight: 500;
|
||||
}
|
||||
&.dark {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Components
|
||||
import textareaQuestion from '@/digital-components/textarea-question/textarea-question';
|
||||
import textareaQuestion from '@/digital-components/textarea-question/textarea-question.vue';
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
|
|
|
|||
|
|
@ -84,15 +84,8 @@ export default {
|
|||
initialValue
|
||||
};
|
||||
|
||||
const { errorMessage,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
validate,
|
||||
errors,
|
||||
resetField }
|
||||
= useField(props.inputId,
|
||||
props.validationRules,
|
||||
fieldOptions);
|
||||
const { errorMessage, handleChange, handleBlur, validate, errors, resetField } =
|
||||
useField(props.inputId, props.validationRules, fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
|
|
@ -105,14 +98,16 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
/**
|
||||
* @summary Returns the number of characters in the textarea field.
|
||||
*/
|
||||
* @summary Returns the number of characters in the textarea field.
|
||||
* @returns {number}
|
||||
*/
|
||||
characterCount() {
|
||||
return this?.modelValue?.length ?? 0;
|
||||
},
|
||||
/**
|
||||
* @summary Returns the CMS text associated with the question.
|
||||
*/
|
||||
* @summary Returns the CMS text associated with the question.
|
||||
* @returns {string} QuestionText from cms data.
|
||||
*/
|
||||
questionText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
||||
},
|
||||
|
|
@ -127,8 +122,9 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
/**
|
||||
* @summary Removes whitespace from the end of pasted content.
|
||||
*/
|
||||
* @summary Removes whitespace from the end of pasted content.
|
||||
* @param {event} evt - Browser event
|
||||
*/
|
||||
trimOnPaste(evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -19,7 +19,9 @@
|
|||
:id="inputId"
|
||||
:ref="inputId"
|
||||
v-model.trim="value"
|
||||
v-maska="mask"
|
||||
v-maska
|
||||
:data-maska="getMaskFromProp"
|
||||
:data-maska-tokens="getTokensFromProp"
|
||||
class="form-control"
|
||||
:type="type"
|
||||
:name="inputId"
|
||||
|
|
@ -30,7 +32,7 @@
|
|||
:aria-label="questionText"
|
||||
:min="min"
|
||||
:max="max"
|
||||
required
|
||||
:required="isRequired"
|
||||
:class="[
|
||||
hasIcon ? 'has-icon' : '',
|
||||
iconRight ? 'icon-right' : '',
|
||||
|
|
@ -40,15 +42,16 @@
|
|||
:maxlength="maxLength ? maxLength : '999'"
|
||||
:data-bs-toggle="includeSelectIcon ? 'modal' : ''"
|
||||
:data-bs-target="'#' + cmsWidgetName"
|
||||
@change="validationRules ? handleChange : () => {}"
|
||||
@blur="validationRules ? handleChange : () => {}"
|
||||
@change="handleChange"
|
||||
@blur="handleChange"
|
||||
@focus="$emit('focus', $event.target.value)"
|
||||
@paste="trimOnPaste"
|
||||
@drop="trimOnPaste" />
|
||||
<button
|
||||
v-if="includeSearchIcon"
|
||||
type="submit"
|
||||
aria-label="Search button" />
|
||||
aria-label="Search button"
|
||||
@click="clickedSearch" />
|
||||
<button
|
||||
v-if="includeSelectIcon"
|
||||
type="submit"
|
||||
|
|
@ -58,6 +61,7 @@
|
|||
</div>
|
||||
<div
|
||||
v-show="errorMessage"
|
||||
ref="errorMessageDiv"
|
||||
class="row my-1 form-test-error">
|
||||
<span
|
||||
class="d-inline-flex mt-0"
|
||||
|
|
@ -95,11 +99,14 @@ export default {
|
|||
iconRight: Boolean, // Place icon on right side of text input, otherwise default is left if hasIcon prop is used
|
||||
hasError: Boolean,
|
||||
mask: {
|
||||
type: String,
|
||||
type: [String, Object],
|
||||
default: ''
|
||||
},
|
||||
validationRules: String,
|
||||
cmsWidgetName: String,
|
||||
cmsWidgetName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxLength: String,
|
||||
questionAlignment: String, // Left or center. Left is default.
|
||||
cornerStyle: String, // Rounded or square. Square is default.
|
||||
|
|
@ -109,19 +116,19 @@ export default {
|
|||
max: String,
|
||||
disableAutoFill: Boolean
|
||||
},
|
||||
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned'],
|
||||
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'],
|
||||
setup(props) {
|
||||
const propsClone = { ...props };
|
||||
const { modelValue } = propsClone;
|
||||
let initialValue;
|
||||
|
||||
switch (typeof modelValue) {
|
||||
case 'number':
|
||||
initialValue = modelValue;
|
||||
break;
|
||||
default:
|
||||
initialValue = modelValue && modelValue.length > 0 ? modelValue : '';
|
||||
break;
|
||||
case 'number':
|
||||
initialValue = modelValue;
|
||||
break;
|
||||
default:
|
||||
initialValue = modelValue && modelValue.length > 0 ? modelValue : '';
|
||||
break;
|
||||
}
|
||||
|
||||
const fieldOptions = {
|
||||
|
|
@ -131,9 +138,8 @@ export default {
|
|||
};
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
const { errorMessage, handleBlur, handleChange, meta, validate, errors } = useField(props.inputId,
|
||||
props.validationRules,
|
||||
fieldOptions);
|
||||
const { errorMessage, handleBlur, handleChange, meta, validate, errors } =
|
||||
useField(props.inputId, props.validationRules, fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
|
|
@ -164,12 +170,12 @@ export default {
|
|||
const words = this.questionText.toString().split(/[ ]+/);
|
||||
words.forEach((word) => {
|
||||
const position = 1;
|
||||
word = [
|
||||
const newWord = [
|
||||
word.toString().slice(0, position),
|
||||
noBreakChar,
|
||||
word.toString().slice(position)
|
||||
].join('');
|
||||
questionText += `${word} `;
|
||||
questionText += `${newWord} `;
|
||||
});
|
||||
|
||||
questionText = questionText.trimEnd();
|
||||
|
|
@ -179,6 +185,21 @@ export default {
|
|||
|
||||
return questionText;
|
||||
}
|
||||
},
|
||||
getMaskFromProp() {
|
||||
if (this.mask) {
|
||||
if (this.mask.mask) {
|
||||
return this.mask.mask;
|
||||
}
|
||||
return this.mask;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getTokensFromProp() {
|
||||
if (this.mask?.tokens) {
|
||||
return `{ '${this.mask.tokens.token}': { 'pattern': '${this.mask.tokens.pattern}' }}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -218,12 +239,18 @@ export default {
|
|||
const blendedString = arrOriginalString.join('');
|
||||
|
||||
this.$emit('update:modelValue', blendedString);
|
||||
},
|
||||
clickedSearch() {
|
||||
if (this.meta.valid) {
|
||||
this.$emit('click-event');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/ux-variables-svg-strings.scss";
|
||||
input[type='date']::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -233,7 +260,7 @@ input[type='date']::-webkit-calendar-picker-indicator {
|
|||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 1px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.1762 1.52764H13.7656V1.50352C13.7656 1.10476 13.6076 0.722334 13.3263 0.44037C13.0451 0.158406 12.6636 0 12.2659 0C11.8681 0 11.4866 0.158406 11.2054 0.44037C10.9241 0.722334 10.7661 1.10476 10.7661 1.50352V1.52764H5.42476V1.50352C5.42476 1.10476 5.26675 0.722334 4.9855 0.44037C4.70424 0.158406 4.32277 0 3.92501 0C3.52725 0 3.14579 0.158406 2.86453 0.44037C2.58327 0.722334 2.42526 1.10476 2.42526 1.50352V1.52764H1.82376C1.34046 1.52891 0.877316 1.72195 0.53557 2.06455C0.193824 2.40716 0.00127018 2.87146 0 3.35598V14.1717C0.0016909 14.656 0.194379 15.1201 0.536035 15.4626C0.87769 15.8051 1.34059 15.9983 1.82376 16H14.1746C14.6581 15.9987 15.1214 15.8057 15.4634 15.4632C15.8054 15.1206 15.9983 14.6563 16 14.1717V3.35598C15.9987 2.87146 15.8062 2.40716 15.4644 2.06455C15.1227 1.72195 14.6595 1.52891 14.1762 1.52764ZM11.8889 1.50352C11.8889 1.4033 11.9286 1.30718 11.9993 1.23631C12.07 1.16544 12.1659 1.12563 12.2659 1.12563C12.3658 1.12563 12.4617 1.16544 12.5324 1.23631C12.6031 1.30718 12.6428 1.4033 12.6428 1.50352V2.99899C12.6428 3.09922 12.6031 3.19534 12.5324 3.2662C12.4617 3.33707 12.3658 3.37688 12.2659 3.37688C12.1659 3.37688 12.07 3.33707 11.9993 3.2662C11.9286 3.19534 11.8889 3.09922 11.8889 2.99899V1.50352ZM3.54807 1.50352C3.54807 1.4033 3.58778 1.30718 3.65847 1.23631C3.72916 1.16544 3.82504 1.12563 3.92501 1.12563C4.02498 1.12563 4.12086 1.16544 4.19155 1.23631C4.26224 1.30718 4.30195 1.4033 4.30195 1.50352V2.99899C4.30195 3.09922 4.26224 3.19534 4.19155 3.2662C4.12086 3.33707 4.02498 3.37688 3.92501 3.37688C3.82504 3.37688 3.72916 3.33707 3.65847 3.2662C3.58778 3.19534 3.54807 3.09922 3.54807 2.99899V1.50352ZM14.8772 14.1717C14.8747 14.3573 14.8001 14.5345 14.6691 14.6658C14.5382 14.797 14.3614 14.8719 14.1762 14.8744H1.82536C1.63995 14.8723 1.4627 14.7976 1.33144 14.6663C1.20018 14.5351 1.12531 14.3575 1.12281 14.1717V6.59296H14.8772V14.1717Z' fill='%23167CAC'/%3E%3Cpath d='M2.33063 11.282H3.93464V12.6006C3.93464 12.7499 3.99379 12.8931 4.09907 12.9986C4.20435 13.1041 4.34715 13.1634 4.49604 13.1634C4.64494 13.1634 4.78773 13.1041 4.89301 12.9986C4.9983 12.8931 5.05745 12.7499 5.05745 12.6006V11.282H7.46346V12.6006C7.46346 12.7499 7.52261 12.8931 7.62789 12.9986C7.73318 13.1041 7.87597 13.1634 8.02486 13.1634C8.17376 13.1634 8.31655 13.1041 8.42184 12.9986C8.52712 12.8931 8.58627 12.7499 8.58627 12.6006V11.282H10.9923V12.6006C10.9923 12.7499 11.0514 12.8931 11.1567 12.9986C11.262 13.1041 11.4048 13.1634 11.5537 13.1634C11.7026 13.1634 11.8454 13.1041 11.9507 12.9986C12.0559 12.8931 12.1151 12.7499 12.1151 12.6006V11.282H13.7191C13.868 11.282 14.0108 11.2227 14.1161 11.1172C14.2214 11.0116 14.2805 10.8685 14.2805 10.7192C14.2805 10.57 14.2214 10.4268 14.1161 10.3213C14.0108 10.2157 13.868 10.1564 13.7191 10.1564H12.1151V8.84425C12.1151 8.69498 12.0559 8.55183 11.9507 8.44628C11.8454 8.34073 11.7026 8.28143 11.5537 8.28143C11.4048 8.28143 11.262 8.34073 11.1567 8.44628C11.0514 8.55183 10.9923 8.69498 10.9923 8.84425V10.1628H8.58627V8.84425C8.58627 8.69498 8.52712 8.55183 8.42184 8.44628C8.31655 8.34073 8.17376 8.28143 8.02486 8.28143C7.87597 8.28143 7.73318 8.34073 7.62789 8.44628C7.52261 8.55183 7.46346 8.69498 7.46346 8.84425V10.1628H5.05745V8.84425C5.05745 8.69498 4.9983 8.55183 4.89301 8.44628C4.78773 8.34073 4.64494 8.28143 4.49604 8.28143C4.34715 8.28143 4.20435 8.34073 4.09907 8.44628C3.99379 8.55183 3.93464 8.69498 3.93464 8.84425V10.1628H2.33063C2.18174 10.1628 2.03894 10.2221 1.93366 10.3277C1.82837 10.4332 1.76923 10.5764 1.76923 10.7257C1.76923 10.8749 1.82837 11.0181 1.93366 11.1236C2.03894 11.2292 2.18174 11.2885 2.33063 11.2885V11.282Z' fill='%23167CAC'/%3E%3C/svg%3E");
|
||||
background-image: url($svg-calendar-picker);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 16px; // check this
|
||||
|
|
@ -255,7 +282,7 @@ input[type='date']::-webkit-calendar-picker-indicator {
|
|||
}
|
||||
span {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
font-size: 0.875rem;
|
||||
color: #4d5151;
|
||||
}
|
||||
.form-test-error span {
|
||||
|
|
@ -268,17 +295,17 @@ input[type='date']::-webkit-calendar-picker-indicator {
|
|||
position: relative;
|
||||
&.has-search-icon {
|
||||
input[type='text'] {
|
||||
border-radius: 50rem;
|
||||
border-radius: $border-radius-lg;
|
||||
}
|
||||
button[type='submit'] {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.7817 14.7328L11.8252 10.7762C12.8833 9.45005 13.3936 7.76911 13.2513 6.07849C13.1091 4.38788 12.325 2.81587 11.0601 1.6852C9.79515 0.554524 8.14538 -0.0490261 6.44946 -0.00154744C4.75353 0.0459312 3.14012 0.740836 1.94045 1.94051C0.740775 3.14018 0.0458701 4.75359 -0.00160848 6.44952C-0.0490871 8.14545 0.554463 9.79521 1.68514 11.0601C2.81581 12.325 4.38782 13.1091 6.07843 13.2514C7.76905 13.3937 9.44999 12.8834 10.7762 11.8252L14.7349 15.7839C14.8044 15.8527 14.8869 15.907 14.9774 15.9439C15.068 15.9808 15.165 15.9995 15.2628 15.9989C15.3606 15.9983 15.4573 15.9784 15.5475 15.9405C15.6376 15.9025 15.7194 15.8471 15.7881 15.7776C15.8568 15.708 15.9112 15.6256 15.9481 15.535C15.985 15.4444 16.0036 15.3474 16.0031 15.2496C16.0025 15.1518 15.9826 15.0551 15.9446 14.965C15.9067 14.8748 15.8513 14.7931 15.7817 14.7243V14.7328ZM6.63737 11.7913C5.61803 11.7913 4.62157 11.4891 3.77402 10.9228C2.92646 10.3564 2.26587 9.5515 1.87578 8.60975C1.4857 7.668 1.38363 6.63172 1.5825 5.63196C1.78136 4.6322 2.27222 3.71386 2.99301 2.99307C3.7138 2.27229 4.63214 1.78142 5.6319 1.58256C6.63166 1.38369 7.66793 1.48576 8.60969 1.87585C9.55144 2.26593 10.3564 2.92652 10.9227 3.77408C11.489 4.62163 11.7913 5.61809 11.7913 6.63743C11.7896 8.00382 11.2461 9.31376 10.2799 10.2799C9.3137 11.2461 8.00376 11.7897 6.63737 11.7913Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
|
||||
background-image: url($svg-search-icon);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
border-radius: 0 50rem 50rem 0;
|
||||
border-radius: 0 $border-radius-lg $border-radius-lg 0;
|
||||
background-color: $blue-100;
|
||||
width: 2.75rem;
|
||||
height: 100%;
|
||||
|
|
@ -293,7 +320,7 @@ input[type='date']::-webkit-calendar-picker-indicator {
|
|||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 1rem;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
background-image: url($svg-select-icon);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: transparent;
|
||||
|
|
@ -325,8 +352,8 @@ input[type='date']::-webkit-calendar-picker-indicator {
|
|||
border: 1px solid $gray-500;
|
||||
border-radius: 0.5rem;
|
||||
min-height: 3rem;
|
||||
max-height: 48px;
|
||||
padding: 12px 16px;
|
||||
max-height: 3rem;
|
||||
padding: 0.75rem 1rem;
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,49 +2,50 @@ import axios from 'axios';
|
|||
import analyticsMixIn from '@/mixins/analytics-mixin.js';
|
||||
import { useMainStore } from '@/store';
|
||||
|
||||
import { applicationConfig } from '@/constants/application-config.js';
|
||||
import applicationConfig from '@/constants/application-config.js';
|
||||
import { GaCategories, GaActions, GaLabels } from '@/constants/analytics';
|
||||
import { headerKeys } from '@/constants/header-keys';
|
||||
import headerKeys from '@/constants/header-keys';
|
||||
|
||||
export default {
|
||||
callHttpClient({ method, endpoint, payload, logApiCall = true}) {
|
||||
callHttpClient({ method, endpoint, payload, logApiCall = true }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const store = useMainStore();
|
||||
const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO;
|
||||
const payloadAndAnalyticsData = Object.assign({}, payload, { AppName: 'SelfService' });
|
||||
const payloadAndAnalyticsData = { ...payload, AppName: 'SelfService' };
|
||||
const headers = {
|
||||
[headerKeys.EXPERIMENT]: JSON.stringify(store.experimentSettings)
|
||||
};
|
||||
|
||||
axios({
|
||||
method: method,
|
||||
axios({
|
||||
method,
|
||||
url: cfDistroUrl + endpoint,
|
||||
data: payloadAndAnalyticsData,
|
||||
crossDomain: true,
|
||||
responseType: 'json',
|
||||
headers: headers,
|
||||
headers
|
||||
})
|
||||
.then((response) => {
|
||||
if (logApiCall) {
|
||||
analyticsMixIn.methods.pushEventToGA(
|
||||
GaCategories.API_RESPONSE,
|
||||
GaActions.RESULT,
|
||||
`${GaLabels.SUCCESS}_${endpoint}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
return resolve(response);
|
||||
},
|
||||
error => {
|
||||
console.error(error);
|
||||
.then(
|
||||
(response) => {
|
||||
if (logApiCall) {
|
||||
analyticsMixIn.methods.pushEventToGA(
|
||||
GaCategories.API_RESPONSE,
|
||||
GaActions.RESULT,
|
||||
`${GaLabels.SUCCESS}_${endpoint}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
return resolve(response);
|
||||
},
|
||||
(error) => {
|
||||
window.console.error(error);
|
||||
|
||||
// implement if analytics service is down
|
||||
if (endpoint.includes('analytics')) {
|
||||
return resolve({data: ''});
|
||||
}
|
||||
// implement if analytics service is down
|
||||
if (endpoint.includes('analytics')) {
|
||||
return resolve({ data: '' });
|
||||
}
|
||||
|
||||
return reject(error.response);
|
||||
}
|
||||
return reject(error.response);
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
|
@ -52,19 +53,18 @@ export default {
|
|||
// used for mocked services
|
||||
async mockCallHttpClient(method, endpoint) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios({
|
||||
method: method,
|
||||
axios({
|
||||
method,
|
||||
url: endpoint,
|
||||
crossDomain: true,
|
||||
responseType: {}
|
||||
})
|
||||
.then((response) => {
|
||||
return resolve(response);
|
||||
},
|
||||
error => {
|
||||
console.error(error);
|
||||
return reject(error.response);
|
||||
}
|
||||
.then(
|
||||
(response) => resolve(response),
|
||||
(error) => {
|
||||
window.console.error(error);
|
||||
return reject(error.response);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,54 @@ import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
|||
jest.mock('axios');
|
||||
jest.mock('@/mixins/analytics-mixin');
|
||||
|
||||
/** @ignore */
|
||||
function setupMocksForHttpClient({
|
||||
endpoint = null,
|
||||
isError = false,
|
||||
additionalData = null
|
||||
}) {
|
||||
// Clear node module
|
||||
axios.mockClear();
|
||||
|
||||
getMountOptions();
|
||||
|
||||
// Success Response
|
||||
const response = {
|
||||
status: 200,
|
||||
data: {
|
||||
message: 'Success',
|
||||
additionalData
|
||||
}
|
||||
};
|
||||
|
||||
// Error Response
|
||||
const error = {
|
||||
response: {
|
||||
status: 500,
|
||||
data: {
|
||||
message: 'Error',
|
||||
additionalData
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Error interceptor on Axios returns a different object, so we need to mimic that.
|
||||
if (isError) {
|
||||
axios.mockRejectedValue(error);
|
||||
} else {
|
||||
axios.mockResolvedValue(response);
|
||||
}
|
||||
|
||||
return {
|
||||
endpoint,
|
||||
logApiCall: true
|
||||
};
|
||||
}
|
||||
|
||||
it('Global Methods - Call Http Client - Should Resolve Promise', () => {
|
||||
// Arrange
|
||||
const endpoint = 'https://mock.safelite.com';
|
||||
const httpArgs = setupMocksForHttpClient({ endpoint: endpoint });
|
||||
const httpArgs = setupMocksForHttpClient({ endpoint });
|
||||
|
||||
// Act
|
||||
globalMethods.callHttpClient(httpArgs).then((response) => {
|
||||
|
|
@ -25,7 +69,7 @@ it('Global Methods - Call Http Client - Should Reject Promise', () => {
|
|||
// Arrange
|
||||
const endpoint = 'https://mock.safelite.com';
|
||||
const httpArgs = setupMocksForHttpClient({
|
||||
endpoint: endpoint,
|
||||
endpoint,
|
||||
isError: true
|
||||
});
|
||||
analyticsMixIn.methods.pushEventToGA = jest.fn();
|
||||
|
|
@ -38,46 +82,3 @@ it('Global Methods - Call Http Client - Should Reject Promise', () => {
|
|||
expect(err.status).toEqual(500);
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocksForHttpClient({
|
||||
endpoint = null,
|
||||
isError = false,
|
||||
additionalData = null
|
||||
}) {
|
||||
// Clear node module
|
||||
axios.mockClear();
|
||||
|
||||
const mountOptions = getMountOptions();
|
||||
|
||||
// Success Response
|
||||
const response = {
|
||||
status: 200,
|
||||
data: {
|
||||
message: 'Success',
|
||||
additionalData: additionalData
|
||||
}
|
||||
};
|
||||
|
||||
// Error Response
|
||||
const error = {
|
||||
response: {
|
||||
status: 500,
|
||||
data: {
|
||||
message: 'Error',
|
||||
additionalData: additionalData
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Error interceptor on Axios returns a different object, so we need to mimic that.
|
||||
if (isError) {
|
||||
axios.mockRejectedValue(error);
|
||||
} else {
|
||||
axios.mockResolvedValue(response);
|
||||
}
|
||||
|
||||
return {
|
||||
endpoint: endpoint,
|
||||
logApiCall: true
|
||||
};
|
||||
}
|
||||
|
|
|
|||
75
src/helpers/add-to-calendar-helper.js
Normal file
75
src/helpers/add-to-calendar-helper.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import calendarStatus from '@/constants/calendar-status';
|
||||
import { getDateFormat } from '@/helpers/date-helper';
|
||||
|
||||
function ToCalendarFileString(str, replacementArg = '<p>') {
|
||||
return str.replace('\r\n', replacementArg);
|
||||
}
|
||||
|
||||
export function getCalendarFile(calFile) {
|
||||
const dateFormat = 'yyyyMMddTHHmmss';
|
||||
const calEvent = [];
|
||||
calEvent.push('BEGIN:VCALENDAR');
|
||||
calEvent.push('VERSION:2.0');
|
||||
calEvent.push('BEGIN:VEVENT');
|
||||
calEvent.push(`DTSTAMP:${calFile.TimeStamp}`);
|
||||
calEvent.push(`UID:${calFile.UniqueId}@safelite.com`);
|
||||
calEvent.push('PRODID:noreply@safelite.com');
|
||||
switch (calFile.Status) {
|
||||
case calendarStatus.BUSY:
|
||||
calEvent.push('X-MICROSOFT-CDO-BUSYSTATUS:BUSY');
|
||||
break;
|
||||
case calendarStatus.FREE:
|
||||
calEvent.push('TRANSP:TRANSPARENT');
|
||||
break;
|
||||
case calendarStatus.TENTATIVE:
|
||||
calEvent.push('STATUS:TENTATIVE');
|
||||
break;
|
||||
case calendarStatus.OUT_OF_THE_OFFICE:
|
||||
calEvent.push('X-MICROSOFT-CDO-BUSYSTATUS:OOF');
|
||||
break;
|
||||
default:
|
||||
// throw new Exception("Invalid CalendarStatus");
|
||||
}
|
||||
if (calFile.AllDayEvent) {
|
||||
calEvent.push(`DTSTART;VALUE=DATE:${getDateFormat(calFile.StartDate, dateFormat)}`);
|
||||
calEvent.push(`DTEND;;VALUE=DATE:${getDateFormat(calFile.EndDate, dateFormat)}`);
|
||||
} else {
|
||||
calEvent.push(`DTSTART:${getDateFormat(calFile.StartDate, dateFormat)}`);
|
||||
calEvent.push(`DTEND:${getDateFormat(calFile.EndDate, dateFormat)}`);
|
||||
}
|
||||
|
||||
calEvent.push(`SUMMARY:${ToCalendarFileString(calFile.Subject, ' ')}`);
|
||||
if (calFile.Location != null && calFile.Location !== '') {
|
||||
calEvent.push(`LOCATION:${ToCalendarFileString(calFile.Location, ' ')}`);
|
||||
}
|
||||
if (calFile.Body != null && calFile.Body !== '') {
|
||||
if (calFile.IsHTML) {
|
||||
calEvent.push(`DESCRIPTION:${ToCalendarFileString(calFile.Body)}`);
|
||||
calEvent.push('X-ALT-DESC;FMTTYPE=text/html:'
|
||||
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">'
|
||||
+ '<HTML><HEAD><TITLE></TITLE></HEAD><BODY>'
|
||||
+ '<P DIR=LTR><SPAN LANG="en-us">'
|
||||
+ `</SPAN>${
|
||||
ToCalendarFileString(calFile.Body)
|
||||
}</BODY></HTML>`);
|
||||
} else {
|
||||
calEvent.push(`DESCRIPTION:${ToCalendarFileString(calFile.Body)}`);
|
||||
}
|
||||
}
|
||||
calEvent.push('END:VEVENT');
|
||||
calEvent.push('END:VCALENDAR');
|
||||
return calEvent.join('\r\n');
|
||||
}
|
||||
|
||||
export function download(filename, fileBody) {
|
||||
const element = document.createElement('a');
|
||||
element.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(fileBody)}`);
|
||||
element.setAttribute('download', filename);
|
||||
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
22
src/helpers/bailout-helper.js
Normal file
22
src/helpers/bailout-helper.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { useMainStore } from '@/store';
|
||||
import BailoutCode from '@/constants/bailoutCode';
|
||||
import issPageValues from "@/router/router-constants/issPage-values";
|
||||
|
||||
function canBailoutNavigateBack() {
|
||||
const code = useMainStore().pageData(issPageValues.BAILOUT_PAGE)?.bailoutCode;
|
||||
if (code == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case BailoutCode.VehicleNotFound:
|
||||
case BailoutCode.DoNotSeeMyShop:
|
||||
case BailoutCode.TPANotEnabled:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default canBailoutNavigateBack;
|
||||
|
|
@ -4,10 +4,18 @@ export function validateISSClientTag(clientTag) {
|
|||
const store = useMainStore();
|
||||
|
||||
return store.validateClientTag(clientTag)
|
||||
.then((response) =>
|
||||
// Success
|
||||
response,
|
||||
(error) =>
|
||||
// Error
|
||||
null);
|
||||
.then(
|
||||
(response) => response,
|
||||
() => null
|
||||
);
|
||||
}
|
||||
|
||||
export function validateISSClientSignature(clientTag, token, signature) {
|
||||
const store = useMainStore();
|
||||
|
||||
return store.validateClientSignature(clientTag, token, signature)
|
||||
.then(
|
||||
(response) => response,
|
||||
() => null
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,102 @@
|
|||
import { dynamicStrings } from '@/constants/dynamic-strings';
|
||||
/* eslint-disable no-use-before-define */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable jsdoc/require-param-type */
|
||||
/* eslint-disable jsdoc/require-param-description */
|
||||
/* eslint-disable jsdoc/require-returns */
|
||||
import dynamicStrings from '@/constants/dynamic-strings';
|
||||
import { useMainStore } from '@/store';
|
||||
|
||||
export function fetchCmsContentForPage(issPage) {
|
||||
const store = useMainStore();
|
||||
const { clientName } = store.issConfig;
|
||||
const { accountNumber } = store.issConfig;
|
||||
const clientOverride = (clientName.length > 0 && accountNumber > 0);
|
||||
|
||||
return store.getPageData(issPage)
|
||||
// Get the base/default page first.
|
||||
.then((baseResponse) => {
|
||||
if (!clientOverride) {
|
||||
// Return the base page if there are no client override.
|
||||
return processPageData(baseResponse, null);
|
||||
}
|
||||
// Else get the client override page.
|
||||
const pageName = `${issPage}_${clientName.toLowerCase().replace(/ /g, '')}`;
|
||||
|
||||
return store.getPageData(pageName)
|
||||
.then((clientResponse) =>
|
||||
// Process the client override if it exists.
|
||||
processPageData(baseResponse, clientResponse),
|
||||
(error) => {
|
||||
console.error(error);
|
||||
// Process the just the base if no client override exists.
|
||||
return processPageData(baseResponse, null);
|
||||
});
|
||||
/**
|
||||
* @function getStringWithCustomValues
|
||||
* @summary Returns str with all custom values replaced in accordance with the provided customValueMap
|
||||
* @param {string} str
|
||||
* @param {Dictionary} customValueMap
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getStringWithCustomValues(str, customValueMap) {
|
||||
let newString = str ?? '';
|
||||
if (customValueMap != null) {
|
||||
Object.keys(customValueMap).forEach((key) => {
|
||||
newString = newString.replaceAll(`{custom:${key}}`, customValueMap[key]);
|
||||
});
|
||||
}
|
||||
return newString;
|
||||
}
|
||||
|
||||
// This function will process the widget item and replace any global state variables with their values.
|
||||
// This is a recursive function, it will call itself until it runs out of items to iterate on given the object.
|
||||
/**
|
||||
* @function processWidgetItemForReplacement
|
||||
* @param widgetModel
|
||||
* @param key
|
||||
*/
|
||||
function processWidgetItemForReplacement(widgetModel, key) {
|
||||
// If we have a string, and it needs to be replaced.
|
||||
if (typeof widgetModel[key] === 'string') {
|
||||
if (widgetModel[key].includes('{if:')) {
|
||||
widgetModel[key] = processIfStatements(
|
||||
widgetModel[key],
|
||||
dynamicStrings.GLOBAL_STATE,
|
||||
getStoreValueFromString
|
||||
);
|
||||
}
|
||||
|
||||
if (widgetModel[key].includes(dynamicStrings.MODAL_LINK)) {
|
||||
widgetModel[key] = mapStringToModal(widgetModel[key]);
|
||||
}
|
||||
if (widgetModel[key].includes(dynamicStrings.EXTERNAL_LINK)) {
|
||||
widgetModel[key] = mapStringToLink(widgetModel[key]);
|
||||
}
|
||||
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
|
||||
widgetModel[key] = mapStringToState(widgetModel[key]);
|
||||
}
|
||||
return widgetModel[key];
|
||||
}
|
||||
|
||||
// If we have an object. array, etc
|
||||
if (typeof widgetModel[key] === 'object' && Object.keys(widgetModel[key]).length) {
|
||||
Object.keys(widgetModel[key]).forEach((item) => {
|
||||
processWidgetItemForReplacement(widgetModel[key], item);
|
||||
});
|
||||
|
||||
return widgetModel[key];
|
||||
}
|
||||
|
||||
// If we have something else like a number, boolean, etc. just return it
|
||||
return widgetModel[key];
|
||||
}
|
||||
|
||||
// Parent function for processWidgetItemForReplacement. This will loop through the parent
|
||||
// object and pass any objects that need additional processing to the processWidgetItemForReplacement function.
|
||||
/**
|
||||
* @function processWidgetItemForfindAndReplaceGlobalStateValuesReplacement
|
||||
* @param widgetModel
|
||||
* @param widgetName
|
||||
*/
|
||||
function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
|
||||
const objWithReplacements = {
|
||||
Name: widgetName,
|
||||
Model: {}
|
||||
};
|
||||
|
||||
Object.keys(widgetModel).forEach((key) => {
|
||||
const modelWithReplacements = processWidgetItemForReplacement(widgetModel, key);
|
||||
|
||||
objWithReplacements.Model[key] = modelWithReplacements;
|
||||
});
|
||||
|
||||
return objWithReplacements;
|
||||
}
|
||||
|
||||
// Support method for processing the page data from the CMS call.
|
||||
// widgets = current widget collection used by page.
|
||||
// baseResponse = contains the widgets from the base page.
|
||||
// clientResponse = contains the widgets from the client override page. (null if none)
|
||||
/**
|
||||
* @function processPageData
|
||||
* @param baseResponse
|
||||
* @param clientResponse
|
||||
*/
|
||||
function processPageData(baseResponse, clientResponse) {
|
||||
const pageDataFromCms = {};
|
||||
let widgets = [];
|
||||
|
|
@ -44,7 +108,7 @@ function processPageData(baseResponse, clientResponse) {
|
|||
if (!clientResponse?.data?.Result) {
|
||||
widgets = baseResponse.data.Result;
|
||||
} else {
|
||||
// Override any base widgets with the client override widgets if found.
|
||||
// Override any base widgets with the client override widgets if found.
|
||||
baseResponse.data.Result.forEach((baseWidget) => {
|
||||
let found = false;
|
||||
clientResponse.data.Result.forEach((clientWidget) => {
|
||||
|
|
@ -75,9 +139,8 @@ function processPageData(baseResponse, clientResponse) {
|
|||
}
|
||||
|
||||
widgets.forEach((widget) => {
|
||||
// Global state value replacement.
|
||||
const widgetWithReplacements = findAndReplaceGlobalStateValues(widget.Model,
|
||||
widget.Name);
|
||||
// Global state value replacement.
|
||||
const widgetWithReplacements = findAndReplaceGlobalStateValues(widget.Model, widget.Name);
|
||||
|
||||
// If we already have this widget, push it on the collection
|
||||
if (widgetWithReplacements.Name in pageDataFromCms) {
|
||||
|
|
@ -85,9 +148,7 @@ function processPageData(baseResponse, clientResponse) {
|
|||
return;
|
||||
}
|
||||
|
||||
pageDataFromCms[widgetWithReplacements.Name] = [
|
||||
widgetWithReplacements.Model
|
||||
];
|
||||
pageDataFromCms[widgetWithReplacements.Name] = [widgetWithReplacements.Model];
|
||||
});
|
||||
|
||||
Object.keys(pageDataFromCms).forEach((key) => {
|
||||
|
|
@ -99,71 +160,97 @@ function processPageData(baseResponse, clientResponse) {
|
|||
return pageDataFromCms;
|
||||
}
|
||||
|
||||
// Parent function for processWidgetItemForReplacement. This will loop through the parent
|
||||
// object and pass any objects that need additional processing to the processWidgetItemForReplacement function.
|
||||
function findAndReplaceGlobalStateValues(widgetModel, widgetName) {
|
||||
const objWithReplacements = {
|
||||
Name: widgetName,
|
||||
Model: {}
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param issPage
|
||||
*/
|
||||
export function fetchCmsContentForPage(issPage) {
|
||||
const store = useMainStore();
|
||||
const { clientName } = store.issConfig;
|
||||
const { parentAccountNumber } = store.issConfig;
|
||||
const clientOverride = clientName.length > 0 && parentAccountNumber > 0;
|
||||
|
||||
Object.keys(widgetModel).forEach((key) => {
|
||||
const modelWithReplacements = processWidgetItemForReplacement(widgetModel,
|
||||
key);
|
||||
return (
|
||||
store
|
||||
.getPageData(issPage)
|
||||
// Get the base/default page first.
|
||||
.then((baseResponse) => {
|
||||
if (!clientOverride) {
|
||||
// Return the base page if there are no client override.
|
||||
return processPageData(baseResponse, null);
|
||||
}
|
||||
// Else get the client override page.
|
||||
const pageName = `${issPage}_${clientName.toLowerCase().replace(/ /g, '')}`;
|
||||
|
||||
objWithReplacements.Model[key] = modelWithReplacements;
|
||||
});
|
||||
|
||||
return objWithReplacements;
|
||||
return store.getPageData(pageName).then(
|
||||
(clientResponse) =>
|
||||
// Process the client override if it exists.
|
||||
processPageData(baseResponse, clientResponse),
|
||||
(error) => {
|
||||
console.error(error);
|
||||
// Process the just the base if no client override exists.
|
||||
return processPageData(baseResponse, null);
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// This function will process the widget item and replace any global state variables with their values.
|
||||
// This is a recursive function, it will call itself until it runs out of items to iterate on given the object.
|
||||
function processWidgetItemForReplacement(widgetModel, key) {
|
||||
// If we have a string, and it needs to be replaced.
|
||||
if (typeof widgetModel[key] === 'string') {
|
||||
if (widgetModel[key].includes('{if:')) {
|
||||
widgetModel[key] = processIfStatements(widgetModel[key],
|
||||
dynamicStrings.GLOBAL_STATE,
|
||||
getStoreValueFromString);
|
||||
}
|
||||
export function fetchGlobalCmsContent(issComponent) {
|
||||
const store = useMainStore();
|
||||
|
||||
if (widgetModel[key].includes(dynamicStrings.MODAL_LINK)) {
|
||||
widgetModel[key] = mapStringToModal(widgetModel[key]);
|
||||
}
|
||||
if (widgetModel[key].includes(dynamicStrings.EXTERNAL_LINK)) {
|
||||
widgetModel[key] = mapStringToLink(widgetModel[key]);
|
||||
}
|
||||
if (widgetModel[key].includes(dynamicStrings.GLOBAL_STATE)) {
|
||||
widgetModel[key] = mapStringToState(widgetModel[key]);
|
||||
}
|
||||
return widgetModel[key];
|
||||
}
|
||||
|
||||
// If we have an object. array, etc
|
||||
if (typeof widgetModel[key] === 'object'
|
||||
&& Object.keys(widgetModel[key]).length) {
|
||||
Object.keys(widgetModel[key]).forEach((item) => {
|
||||
processWidgetItemForReplacement(widgetModel[key], item);
|
||||
});
|
||||
|
||||
return widgetModel[key];
|
||||
}
|
||||
|
||||
// If we have something else like a number, boolean, etc. just return it
|
||||
return widgetModel[key];
|
||||
return (
|
||||
store.getPageData(issComponent)
|
||||
.then((baseResponse) => processPageData(baseResponse, null)));
|
||||
}
|
||||
|
||||
export function updateCmsSiteHeader(cmsResponse) {
|
||||
const clientCmsSiteHeader = getCmsSiteHeader(cmsResponse);
|
||||
useMainStore().issConfig.clientHeader = clientCmsSiteHeader;
|
||||
}
|
||||
|
||||
function getCmsSiteHeader(cmsResponse) {
|
||||
if (cmsResponse?.SiteHeaderWidget) {
|
||||
const siteHeader = cmsResponse?.SiteHeaderWidget;
|
||||
if (siteHeader.Answers) {
|
||||
const { parentAccountNumber } = useMainStore().issConfig;
|
||||
let headerAnswersToUse = siteHeader.Answers
|
||||
.find((answer) => answer.AccountNumber === parentAccountNumber.toString());
|
||||
if (headerAnswersToUse == null) {
|
||||
headerAnswersToUse = siteHeader.Answers
|
||||
.find((answer) => answer.AccountNumber === '0');
|
||||
}
|
||||
|
||||
if (headerAnswersToUse) {
|
||||
return {
|
||||
altText: headerAnswersToUse.AltText,
|
||||
clientName: headerAnswersToUse.ClientName,
|
||||
hexCode: headerAnswersToUse.HexCode,
|
||||
imageId: headerAnswersToUse.ImageId,
|
||||
imageUrl: headerAnswersToUse.AnswerImageUrl
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @function mapStringToModal
|
||||
* @param str
|
||||
*/
|
||||
function mapStringToModal(str) {
|
||||
const startIndex = str.indexOf(`{${dynamicStrings.MODAL_LINK}`);
|
||||
let linkToReplace = str.substring(startIndex, str.length);
|
||||
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
|
||||
|
||||
const params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length - 1);
|
||||
const params = linkToReplace.substring(
|
||||
dynamicStrings.MODAL_LINK.length + 2,
|
||||
linkToReplace.length - 1
|
||||
);
|
||||
const splitParams = params.split(',');
|
||||
|
||||
const bodyText
|
||||
= `<a href="#" modalTarget="${splitParams[0]}" class="modal-text" aria-label="Modal window">${splitParams[1]}</a>`;
|
||||
const bodyText = `<a href="#" modalTarget="${splitParams[0]}" class="modal-text" aria-label="Modal window">${splitParams[1]}</a>`;
|
||||
|
||||
let returnVal = str.replace(linkToReplace, bodyText);
|
||||
|
||||
|
|
@ -173,12 +260,19 @@ function mapStringToModal(str) {
|
|||
return returnVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @function mapStringToLink
|
||||
* @param str
|
||||
*/
|
||||
function mapStringToLink(str) {
|
||||
const startIndex = str.indexOf(`{${dynamicStrings.EXTERNAL_LINK}`);
|
||||
let linkToReplace = str.substring(startIndex, str.length);
|
||||
linkToReplace = linkToReplace.substring(0, linkToReplace.indexOf('}') + 1);
|
||||
|
||||
const params = linkToReplace.substring((dynamicStrings.EXTERNAL_LINK).length + 2, linkToReplace.length - 1);
|
||||
const params = linkToReplace.substring(
|
||||
dynamicStrings.EXTERNAL_LINK.length + 2,
|
||||
linkToReplace.length - 1
|
||||
);
|
||||
const splitParams = params.split(',');
|
||||
|
||||
const bodyText = `<a href="${splitParams[0]}" class="external-text" target="_blank">${splitParams[1]}</a>`;
|
||||
|
|
@ -192,6 +286,10 @@ function mapStringToLink(str) {
|
|||
}
|
||||
|
||||
// Function to convert a string, into a matching global state item.
|
||||
/**
|
||||
* @function mapStringToState
|
||||
* @param str
|
||||
*/
|
||||
function mapStringToState(str) {
|
||||
// Pull all matches out of the string.
|
||||
const regexExp = /{([^{}]*?):([^{}]*?)}/g;
|
||||
|
|
@ -201,11 +299,12 @@ function mapStringToState(str) {
|
|||
// Our final string value that will be built from the matches.
|
||||
const stringBuilder = '';
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const match of globalStateMatches) {
|
||||
// Reset store state for each match.
|
||||
const valueFromStore = getStoreValueFromString(match[2]);
|
||||
if (!valueFromStore) {
|
||||
console.warning('Unable to resolve global state data.');
|
||||
console.warn('Unable to resolve global state data.');
|
||||
return ''; // if we can't map our string to state data, return an empty string.
|
||||
}
|
||||
const stringWithReplacement = str.replace(match[0], valueFromStore);
|
||||
|
|
@ -222,13 +321,20 @@ function mapStringToState(str) {
|
|||
return str.trimStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* @function getStoreValueFromString
|
||||
* @param str
|
||||
*/
|
||||
function getStoreValueFromString(str) {
|
||||
if (!str) return '';
|
||||
|
||||
let storeOrStateObject = useMainStore();
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const s of str.split('.')) {
|
||||
if (s === 'getters') continue; // For backward compatibility
|
||||
if (storeOrStateObject[s] != undefined) {
|
||||
// TODO: I don't think this next line is doing what they think it's doing.
|
||||
// eslint-disable-next-line eqeqeq, valid-typeof
|
||||
if (typeof storeOrStateObject[s] != undefined) {
|
||||
storeOrStateObject = storeOrStateObject[s];
|
||||
} else {
|
||||
break;
|
||||
|
|
@ -251,29 +357,38 @@ function getStoreValueFromString(str) {
|
|||
export function processIfStatements(str, ifConditionKeyword, replacePlaceholderCallback) {
|
||||
const containsRelevantIfStatement = new RegExp(`{if:${ifConditionKeyword}:.+?}`, 'g').test(str);
|
||||
|
||||
const hasEmbeddedCrLf = /\r?\n|\r/g.test(str);
|
||||
if (!containsRelevantIfStatement) {
|
||||
return str;
|
||||
}
|
||||
const ifStatementRegexExpression = getIfStatementRegexExpression();
|
||||
const ifStatementRegexMatches = [...str.matchAll(ifStatementRegexExpression)];
|
||||
const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(ifStatementRegexMatches,
|
||||
ifConditionKeyword);
|
||||
executeIfStatementAndSetProcessedStrings(completeIfStatementArray,
|
||||
replacePlaceholderCallback);
|
||||
const completeIfStatementArray = getAndFlagFirstNonNestedIfStatementWithKeyword(
|
||||
ifStatementRegexMatches,
|
||||
ifConditionKeyword
|
||||
);
|
||||
executeIfStatementAndSetProcessedStrings(completeIfStatementArray, replacePlaceholderCallback);
|
||||
const reconstructedPostProcessedString = joinProcessedRegexArray(ifStatementRegexMatches);
|
||||
return processIfStatements(reconstructedPostProcessedString,
|
||||
return processIfStatements(
|
||||
reconstructedPostProcessedString,
|
||||
ifConditionKeyword,
|
||||
replacePlaceholderCallback);
|
||||
replacePlaceholderCallback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param matches
|
||||
* @param ifConditionKeyword
|
||||
*/
|
||||
function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyword) {
|
||||
let index = 0;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const match of matches) {
|
||||
if (match.groups.isIfStatement && match.groups.ifConditionType === ifConditionKeyword) {
|
||||
let interiorIndex = 0;
|
||||
let nestedLevel = 0;
|
||||
let elseStatementIndex = null;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const interiorMatch of matches.slice(index + 1)) {
|
||||
if (interiorMatch.groups.isIfStatement) {
|
||||
if (interiorMatch.groups.ifConditionType === ifConditionKeyword) {
|
||||
|
|
@ -303,6 +418,11 @@ function getAndFlagFirstNonNestedIfStatementWithKeyword(matches, ifConditionKeyw
|
|||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param matches
|
||||
* @param elseStatementIndex
|
||||
*/
|
||||
function flagMatchesForProcessing(matches, elseStatementIndex) {
|
||||
matches[0].isFlaggedForProcessing = true;
|
||||
matches[matches.length - 1].isFlaggedForProcessing = true;
|
||||
|
|
@ -311,6 +431,10 @@ function flagMatchesForProcessing(matches, elseStatementIndex) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param regexMatches
|
||||
*/
|
||||
function joinProcessedRegexArray(regexMatches) {
|
||||
let processedString = '';
|
||||
regexMatches.forEach((match) => {
|
||||
|
|
@ -320,6 +444,11 @@ function joinProcessedRegexArray(regexMatches) {
|
|||
return processedString;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ifStatementArray
|
||||
* @param replacePlaceholderCallback
|
||||
*/
|
||||
function executeIfStatementAndSetProcessedStrings(ifStatementArray, replacePlaceholderCallback) {
|
||||
const ifCondition = replacePlaceholderCallback(ifStatementArray[0].groups.ifCondition);
|
||||
let isInsideDesiredBlock = ifCondition;
|
||||
|
|
@ -334,6 +463,11 @@ function executeIfStatementAndSetProcessedStrings(ifStatementArray, replacePlace
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param entry
|
||||
* @param isInsideDesiredBlock
|
||||
*/
|
||||
function setProcessedStringOnEntry(entry, isInsideDesiredBlock) {
|
||||
if (!isInsideDesiredBlock) {
|
||||
entry.groups.processedString = '';
|
||||
|
|
@ -352,56 +486,58 @@ function setProcessedStringOnEntry(entry, isInsideDesiredBlock) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function getIfStatementRegexExpression() {
|
||||
// Matches but does not capture:
|
||||
// {if:...} or {else} or {end}
|
||||
const anyLogicOperatorNonCapture = '(?:{(?:end|else|if:.*?)})';
|
||||
// NOTE: ?<variableName> syntax stores the captured match like so: match.groups.variableName
|
||||
const matchStartOfString
|
||||
= '(?<processedString>^.+?)' // Match and Capture all characters (lazy), cannot be empty
|
||||
const matchStartOfString =
|
||||
'(?<processedString>^.+?)' // Match and Capture all characters (lazy), cannot be empty
|
||||
+ '(?=(?:{if))'; // Looks ahead but does not capture {if
|
||||
const matchIfOperator
|
||||
= '(?<isIfStatement>{if:)' // Match & Capture {if:
|
||||
const matchIfOperator =
|
||||
'(?<isIfStatement>{if:)' // Match & Capture {if:
|
||||
+ '(?<ifConditionType>.*?):' // Match all chars up to and including next ':' - Capture all chars up to ':'
|
||||
+ '(?<ifCondition>.*?)}' // Match all chars up to and including next '}' - Capture all chars up to '}'
|
||||
+ '(?<ifTrailingString>.*?)' // Match and Capture all characters (lazy), can be empty
|
||||
+ `(?=${
|
||||
anyLogicOperatorNonCapture
|
||||
})`; // Looks ahead but does not capture the next logic operator
|
||||
const matchElseOperator
|
||||
= '(?<isElseStatement>{else})' // Match & Capture {else}
|
||||
+ `(?=${anyLogicOperatorNonCapture})`; // Looks ahead but does not capture the next logic operator
|
||||
const matchElseOperator =
|
||||
'(?<isElseStatement>{else})' // Match & Capture {else}
|
||||
+ '(?<elseTrailingString>.*?)' // Match & Capture all characters (lazy), can be empty
|
||||
+ `(?=${
|
||||
anyLogicOperatorNonCapture
|
||||
})`; // Looks ahead but does not capture the next logic operator
|
||||
const matchEndOperator
|
||||
= '(?<isEndStatement>{end})' // Match & Capture {end}
|
||||
+ `(?=${anyLogicOperatorNonCapture})`; // Looks ahead but does not capture the next logic operator
|
||||
const matchEndOperator =
|
||||
'(?<isEndStatement>{end})' // Match & Capture {end}
|
||||
+ '(?<endTrailingString>.*?)' // Match & Capture all characters (lazy), can be empty
|
||||
+ `(?=${
|
||||
anyLogicOperatorNonCapture
|
||||
}|$)`; // Looks ahead but does not capture the next logic operator
|
||||
+ `(?=${anyLogicOperatorNonCapture}|$)`; // Looks ahead but does not capture the next logic operator
|
||||
// Combine all matching patterns, separated by 'or' pipes
|
||||
return new RegExp(`${matchStartOfString
|
||||
}|${
|
||||
matchIfOperator
|
||||
}|${
|
||||
matchElseOperator
|
||||
}|${
|
||||
matchEndOperator}`,
|
||||
'g');
|
||||
return new RegExp(
|
||||
`${matchStartOfString}|${matchIfOperator}|${matchElseOperator}|${matchEndOperator}`,
|
||||
'g'
|
||||
);
|
||||
}
|
||||
|
||||
/// ///////////////////////////////////////
|
||||
// End of If Statement Processing Logic //
|
||||
/// ///////////////////////////////////////
|
||||
|
||||
/**
|
||||
*
|
||||
* @param copy
|
||||
*/
|
||||
export function doesCopyContainTextLink(copy) {
|
||||
return copy.includes(dynamicStrings.TEXT_LINK);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
export function setupModalLinks(context) {
|
||||
context.$nextTick(() => {
|
||||
const elements = document.getElementsByClassName('modal-text');
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const element of elements) {
|
||||
const target = element.getAttribute('modalTarget');
|
||||
if (target) {
|
||||
|
|
@ -411,12 +547,31 @@ export function setupModalLinks(context) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param context
|
||||
* @param desiredTarget
|
||||
*/
|
||||
export function setupModalLink(context, desiredTarget) {
|
||||
context.$nextTick(() => {
|
||||
const element = document.querySelector(`[modalTarget=${desiredTarget}]`);
|
||||
if (element) {
|
||||
element.addEventListener('click', () => context.$refs[desiredTarget].openModal());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param copy
|
||||
*/
|
||||
export function doesCopyContainRouterLink(copy) {
|
||||
return copy.includes(this.dynamicStrings.ROUTER_LINK);
|
||||
}
|
||||
|
||||
/**
|
||||
* splits copy on { ... } such as {routerlink: ...}
|
||||
* @param copy
|
||||
* @returns array of strings
|
||||
*/
|
||||
export function splitCopyOnCMSPlaceHolder(copy) {
|
||||
|
|
@ -424,8 +579,19 @@ export function splitCopyOnCMSPlaceHolder(copy) {
|
|||
return copy.split(/{(.*?)}/g);
|
||||
}
|
||||
|
||||
export function getExternalLink(copy) {
|
||||
const url = copy.split(':')[1].split(',')[0];
|
||||
if (url.includes('https-')) {
|
||||
const prefixAdded = url.replace('https-', 'https://');
|
||||
return prefixAdded;
|
||||
}
|
||||
|
||||
return '#!';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string2 of input following this pattern: {string1:string2,string3}
|
||||
* @param copy
|
||||
* @returns string
|
||||
*/
|
||||
export function getRouterLinkRouteFromCopy(copy) {
|
||||
|
|
@ -437,6 +603,7 @@ export function getRouterLinkRouteFromCopy(copy) {
|
|||
|
||||
/**
|
||||
* Returns string3 of input following this pattern: { string1: string2, string3 }
|
||||
* @param copy
|
||||
* @returns string
|
||||
*/
|
||||
export function getRouterLinkDisplayTextFromCopy(copy) {
|
||||
|
|
@ -446,10 +613,23 @@ export function getRouterLinkDisplayTextFromCopy(copy) {
|
|||
return copy.split(':')[1].split(',')[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a router link as an 'a' tag element
|
||||
* @param copy
|
||||
* @returns string
|
||||
*/
|
||||
export function getRouterLinkHtmlStringFromCopy(copy) {
|
||||
return `<a href='/?issPage=${getRouterLinkRouteFromCopy(copy)}' class='router-link'>${getRouterLinkDisplayTextFromCopy(copy)}</a>`;
|
||||
}
|
||||
|
||||
// Copy returned from the CMS that has newlines will return blocks wrapped in
|
||||
// <p ... >...</p>
|
||||
// This function returns an array of each paragraph, works with or without html
|
||||
// attributes present
|
||||
/**
|
||||
*
|
||||
* @param copy
|
||||
*/
|
||||
export function splitCMSCopyOnParagraphTag(copy) {
|
||||
// filter removes empty strings that are a result of string.split with regex
|
||||
return copy.split(/(?:<p(?:.*?)>)|(?:<\/p>)/g).filter((paragraph) => paragraph !== '');
|
||||
|
|
|
|||
18
src/helpers/cms-content-helper.spec.js
Normal file
18
src/helpers/cms-content-helper.spec.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { getStringWithCustomValues } from '@/helpers/cms-content-helper.js';
|
||||
|
||||
describe('getStringWithCustomValues', () => {
|
||||
test.each([
|
||||
['Hello, {custom:name}!', { name: 'John' }, 'Hello, John!'],
|
||||
['{custom:greeting}, {custom:name}!', { greeting: 'Hi', name: 'John' }, 'Hi, John!'],
|
||||
['{custom:greeting}, {custom:name}!', { greeting: 'Hi' }, 'Hi, {custom:name}!'],
|
||||
['Hello, {custom:name}!', {}, 'Hello, {custom:name}!'],
|
||||
['Hello, world!', { name: 'John' }, 'Hello, world!'],
|
||||
['', { name: 'John' }, ''],
|
||||
['Hello, {custom:name}!', null, 'Hello, {custom:name}!'],
|
||||
['Hello, {custom:name}!', undefined, 'Hello, {custom:name}!'],
|
||||
[null, { name: 'John' }, ''],
|
||||
[undefined, { name: 'John' }, '']
|
||||
])('getStringWithCustomValues(%s, %o) should return %s', (str, customValueMap, expected) => {
|
||||
expect(getStringWithCustomValues(str, customValueMap)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,23 +1,78 @@
|
|||
import { cookieNames } from '@/constants/cookie-names';
|
||||
import { applicationConfig } from '@/constants/application-config';
|
||||
import cookieNames from '@/constants/cookie-names';
|
||||
import applicationConfig from '@/constants/application-config';
|
||||
import { useMainStore } from '@/store';
|
||||
|
||||
/*
|
||||
Will update the cookie if present, or create a new one if not.
|
||||
*/
|
||||
export function updateOrCreateISSCookie() {
|
||||
const store = useMainStore();
|
||||
/**
|
||||
* @function isLocalhost
|
||||
*/
|
||||
function isLocalhost() {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
return location.hostname.includes('localhost');
|
||||
}
|
||||
|
||||
// Set up cookie with all the props.
|
||||
setISSCookieProperties({
|
||||
LastTouched: new Date().toUTCString(),
|
||||
SavedSessionTimeoutDate: store.applicationUser.savedSessionTimeout,
|
||||
ShouldResetState: false,
|
||||
ReferralNumber: store.order.referralNumber,
|
||||
ReferralDate: store.order.referralDate,
|
||||
ReferralCorrelationId: store.order.referralCorrelationId,
|
||||
ReferralParentAccountNumber: store.order.accountNumber
|
||||
});
|
||||
/**
|
||||
* @function getCookieValueByName
|
||||
* @param {string} name
|
||||
* @summary
|
||||
* Gets cookie value by name, returns empty string if not found.
|
||||
*/
|
||||
function getCookieValueByName(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
|
||||
if (parts.length === 2) {
|
||||
return parts.pop().split(';').shift();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/*
|
||||
Gets current domain without the subdomain for cookie.
|
||||
*/
|
||||
function getDomainWithoutSubdomain() {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const url = location.hostname;
|
||||
if (isLocalhost()) {
|
||||
return 'localhost';
|
||||
}
|
||||
|
||||
const urlParts = url.split('.');
|
||||
|
||||
return `.${urlParts
|
||||
.slice(0)
|
||||
.slice(-(urlParts.length === 4 ? 3 : 2))
|
||||
.join('.')}`;
|
||||
}
|
||||
|
||||
/*
|
||||
Gets cookie domain value. Localhost will be empty "".
|
||||
*/
|
||||
export function getCookieDomainValue() {
|
||||
return isLocalhost() ? '' : `domain=${getDomainWithoutSubdomain()};`;
|
||||
}
|
||||
|
||||
/*
|
||||
Used to create a cookie.
|
||||
`useDefaultISSCookieAttributes` will set the path and domain to our defaults
|
||||
*/
|
||||
function createOrUpdateCookie(
|
||||
key,
|
||||
value = '',
|
||||
{ useDefaultISSCookieAttributes = true, maxAge, isSecure = true }
|
||||
) {
|
||||
let cookieToAdd = `${key}=${value}; `;
|
||||
|
||||
if (useDefaultISSCookieAttributes) {
|
||||
cookieToAdd += `path=${applicationConfig.COOKIE_PATH}; ${getCookieDomainValue()} `;
|
||||
}
|
||||
if (isSecure && !isLocalhost()) {
|
||||
cookieToAdd += 'secure; ';
|
||||
}
|
||||
if (!Number.isNaN(maxAge)) {
|
||||
cookieToAdd += `max-age=${maxAge};`;
|
||||
}
|
||||
|
||||
document.cookie = cookieToAdd;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -37,6 +92,44 @@ export function getISSCookie() {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Used to set properties on the ISS cookie.
|
||||
Takes an object with properties to set. Will overwrite existing properties.
|
||||
*/
|
||||
function setISSCookieProperties(properties) {
|
||||
if (typeof properties === 'object') {
|
||||
const cookie = getISSCookie();
|
||||
|
||||
if (cookie !== null) {
|
||||
Object.keys(properties).forEach((key) => {
|
||||
cookie[key] = properties[key];
|
||||
});
|
||||
}
|
||||
|
||||
const cookieValueJson = JSON.stringify(cookie ?? {});
|
||||
createOrUpdateCookie(cookieNames.ISS_SESSION_INFO, cookieValueJson, {});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Will update the cookie if present, or create a new one if not.
|
||||
*/
|
||||
export function updateOrCreateISSCookie() {
|
||||
const store = useMainStore();
|
||||
|
||||
// Set up cookie with all the props.
|
||||
setISSCookieProperties({
|
||||
LastTouched: new Date().toUTCString(),
|
||||
SavedSessionTimeoutDate: store.applicationUser.savedSessionTimeout,
|
||||
ShouldResetState: false,
|
||||
ReferralNumber: store.order.referralNumber,
|
||||
ReferralDate: store.order.referralDate,
|
||||
ReferralCorrelationId: store.order.referralCorrelationId,
|
||||
ReferralParentAccountNumber: store.order.accountNumber,
|
||||
SavedSessionId: store.applicationUser.savedSessionId
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Removes cookie from browser.
|
||||
*/
|
||||
|
|
@ -44,13 +137,6 @@ export function deleteISSCookie() {
|
|||
createOrUpdateCookie(cookieNames.ISS_SESSION_INFO, undefined, { maxAge: 0 });
|
||||
}
|
||||
|
||||
/*
|
||||
Gets cookie domain value. Localhost will be empty "".
|
||||
*/
|
||||
export function getCookieDomainValue() {
|
||||
return isLocalhost() ? '' : `domain=${getDomainWithoutSubdomain()};`;
|
||||
}
|
||||
|
||||
/*
|
||||
Gets value of dxdev cookie, and then extracts "did" value from it.
|
||||
Returns empty string if cookie not found or "did" string not present.
|
||||
|
|
@ -106,8 +192,10 @@ export function updateSessionIdCookie() {
|
|||
createOrUpdateCookie(cookieNames.SESSION_ID, getSessionIdValue(), { maxAge: 60 * 30 });
|
||||
}
|
||||
|
||||
export function setCookieProperties(properties,
|
||||
{ useDefaultISSCookieAttributes = true, maxAge, isSecure }) {
|
||||
export function setCookieProperties(
|
||||
properties,
|
||||
{ useDefaultISSCookieAttributes = true, maxAge, isSecure }
|
||||
) {
|
||||
if (typeof properties === 'object') {
|
||||
Object.keys(properties).forEach((key) => {
|
||||
createOrUpdateCookie(key, properties[key], {
|
||||
|
|
@ -118,83 +206,3 @@ export function setCookieProperties(properties,
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===========================
|
||||
= PRIVATE FUNCTIONS =
|
||||
===========================
|
||||
*/
|
||||
|
||||
/*
|
||||
Used to set properties on the ISS cookie.
|
||||
Takes an object with properties to set. Will overwrite existing properties.
|
||||
*/
|
||||
function setISSCookieProperties(properties) {
|
||||
if (typeof properties === 'object') {
|
||||
const cookie = getISSCookie();
|
||||
|
||||
if (cookie !== null) {
|
||||
Object.keys(properties).forEach((key) => {
|
||||
cookie[key] = properties[key];
|
||||
});
|
||||
}
|
||||
|
||||
const cookieValueJson = JSON.stringify(cookie ?? {});
|
||||
createOrUpdateCookie(cookieNames.ISS_SESSION_INFO, cookieValueJson, {});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Used to create a cookie.
|
||||
`useDefaultISSCookieAttributes` will set the path and domain to our defaults
|
||||
*/
|
||||
function createOrUpdateCookie(key, value = '',
|
||||
{ useDefaultISSCookieAttributes = true, maxAge, isSecure = true }) {
|
||||
let cookieToAdd = `${key}=${value}; `;
|
||||
|
||||
if (useDefaultISSCookieAttributes) {
|
||||
cookieToAdd += `path=${applicationConfig.COOKIE_PATH}; ${getCookieDomainValue()} `;
|
||||
}
|
||||
if (isSecure && !isLocalhost()) {
|
||||
cookieToAdd += 'secure; ';
|
||||
}
|
||||
if (!Number.isNaN(maxAge)) {
|
||||
cookieToAdd += `max-age=${maxAge};`;
|
||||
}
|
||||
|
||||
document.cookie = cookieToAdd;
|
||||
}
|
||||
|
||||
/*
|
||||
Gets current domain without the subdomain for cookie.
|
||||
*/
|
||||
function getDomainWithoutSubdomain() {
|
||||
const url = location.hostname;
|
||||
if (isLocalhost()) {
|
||||
return 'localhost';
|
||||
}
|
||||
|
||||
const urlParts = url.split('.');
|
||||
|
||||
return `.${urlParts
|
||||
.slice(0)
|
||||
.slice(-(urlParts.length === 4 ? 3 : 2))
|
||||
.join('.')}`;
|
||||
}
|
||||
|
||||
/*
|
||||
Gets cookie value by name, returns empty string if not found.
|
||||
*/
|
||||
function getCookieValueByName(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
|
||||
if (parts.length === 2) {
|
||||
return parts.pop().split(';').shift();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function isLocalhost() {
|
||||
return location.hostname.includes('localhost');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import damageCustomLabels from '@/constants/damage-custom-labels';
|
||||
import { damageLocationsSelected } from '@/constants/damage-locations-selected';
|
||||
import damageLocationsSelected from '@/constants/damage-locations-selected';
|
||||
import { useMainStore } from '@/store';
|
||||
|
||||
export function getDamageString() {
|
||||
|
|
@ -19,15 +19,15 @@ export function getDamageString() {
|
|||
const { glassLocation } = damageLocations[0];
|
||||
if (glassLocation) {
|
||||
switch (glassLocation) {
|
||||
case damageLocationsSelected.WINDSHIELD:
|
||||
return damageCustomLabels.WINDSHIELD;
|
||||
case damageLocationsSelected.DRIVER:
|
||||
case damageLocationsSelected.PASSENGER:
|
||||
return damageCustomLabels.SIDE_WINDOW;
|
||||
case damageLocationsSelected.REAR:
|
||||
return damageCustomLabels.REAR_WINDOW;
|
||||
default:
|
||||
return '';
|
||||
case damageLocationsSelected.WINDSHIELD:
|
||||
return damageCustomLabels.WINDSHIELD;
|
||||
case damageLocationsSelected.DRIVER:
|
||||
case damageLocationsSelected.PASSENGER:
|
||||
return damageCustomLabels.SIDE_WINDOW;
|
||||
case damageLocationsSelected.REAR:
|
||||
return damageCustomLabels.REAR_WINDOW;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +44,7 @@ function hasMatchingReplacementOption(vehicleDamageOptions, selectedGlassToRepla
|
|||
Rear: 'backGlassOptions'
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const glassToReplace of selectedGlassToReplace) {
|
||||
const propName = optionsMap[glassToReplace.glassLocation];
|
||||
const { availableReplacementOptions } = vehicleDamageOptions[propName];
|
||||
|
|
|
|||
71
src/helpers/damage-review-content-generator.js
Normal file
71
src/helpers/damage-review-content-generator.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import damageLocationsSelected from '@/constants/damage-locations-selected.js';
|
||||
|
||||
function getLocationAnswer(damageLocation, locationAnswers) {
|
||||
return locationAnswers?.find((answer) => answer.Name === damageLocation);
|
||||
}
|
||||
|
||||
function getGlassPieces(damageLocation, glassToReplace) {
|
||||
return glassToReplace?.filter((item) => item.glassLocation === damageLocation);
|
||||
}
|
||||
|
||||
function getWindshieldCopy(answerContent, glassToReplace, isRepair) {
|
||||
const windshieldPieces = getGlassPieces(damageLocationsSelected.WINDSHIELD, glassToReplace);
|
||||
return isRepair || !!windshieldPieces?.length ? [answerContent?.Text] : [];
|
||||
}
|
||||
|
||||
function getRearCopy(answerContent, glassToReplace) {
|
||||
const rearPieces = getGlassPieces(damageLocationsSelected.REAR, glassToReplace);
|
||||
return rearPieces?.length ? [answerContent?.Text] : [];
|
||||
}
|
||||
|
||||
function generateBulletedListFromAnswers(answers) {
|
||||
const items = answers.map((answer) => `<li>${answer.Text}</li>`);
|
||||
return `<ul>${items.join('')}</ul>`;
|
||||
}
|
||||
|
||||
function getSideCopy(location, locationAnswers, damageAnswers, glassToReplace) {
|
||||
const sideItems = getGlassPieces(location, glassToReplace);
|
||||
const damageAnswersOnOrder = damageAnswers?.filter((answer) =>
|
||||
sideItems?.some((glassPiece) => answer.Name === glassPiece.glassName));
|
||||
|
||||
return sideItems?.length
|
||||
? [
|
||||
locationAnswers?.Text,
|
||||
generateBulletedListFromAnswers(damageAnswersOnOrder)
|
||||
]
|
||||
: [];
|
||||
}
|
||||
|
||||
function getDamageDisplayContent(
|
||||
locationAnswers,
|
||||
driverSideDamageAnswers,
|
||||
passengerSideDamageAnswers,
|
||||
glassToReplace,
|
||||
isRepair
|
||||
) {
|
||||
return [
|
||||
...getWindshieldCopy(
|
||||
getLocationAnswer(damageLocationsSelected.WINDSHIELD, locationAnswers),
|
||||
glassToReplace,
|
||||
isRepair
|
||||
),
|
||||
...getSideCopy(
|
||||
damageLocationsSelected.DRIVER,
|
||||
getLocationAnswer(damageLocationsSelected.DRIVER, locationAnswers),
|
||||
driverSideDamageAnswers,
|
||||
glassToReplace
|
||||
),
|
||||
...getSideCopy(
|
||||
damageLocationsSelected.PASSENGER,
|
||||
getLocationAnswer(damageLocationsSelected.PASSENGER, locationAnswers),
|
||||
passengerSideDamageAnswers,
|
||||
glassToReplace
|
||||
),
|
||||
...getRearCopy(
|
||||
getLocationAnswer(damageLocationsSelected.REAR, locationAnswers),
|
||||
glassToReplace
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
export { getDamageDisplayContent, getLocationAnswer };
|
||||
411
src/helpers/damage-review-content-generator.spec.js
Normal file
411
src/helpers/damage-review-content-generator.spec.js
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
import { getDamageDisplayContent, getLocationAnswer } from '@/helpers/damage-review-content-generator.js';
|
||||
import damageLocationsSelected from '@/constants/damage-locations-selected';
|
||||
|
||||
const testConstants = {
|
||||
cmsConstants: {
|
||||
widgetNames: {
|
||||
locations: 'DamageLocationsWidget',
|
||||
driverDamages: 'DriverDamagesWidget',
|
||||
passengerDamages: 'PassengerDamagesWidget'
|
||||
},
|
||||
damageLocations: {
|
||||
windshield: damageLocationsSelected.WINDSHIELD,
|
||||
driver: damageLocationsSelected.DRIVER,
|
||||
passenger: damageLocationsSelected.PASSENGER,
|
||||
rear: damageLocationsSelected.REAR
|
||||
},
|
||||
damageNames: {
|
||||
vent: damageLocationsSelected.VENT,
|
||||
front: damageLocationsSelected.FRONT,
|
||||
back: damageLocationsSelected.BACK,
|
||||
quarter: damageLocationsSelected.QUARTER,
|
||||
side: damageLocationsSelected.SIDEDOOR
|
||||
},
|
||||
locationCopy: {
|
||||
windshield: 'Windshield copy',
|
||||
driver: 'Driver copy',
|
||||
passenger: 'Passenger copy',
|
||||
rear: 'Rear copy'
|
||||
},
|
||||
damageCopy: {
|
||||
vent: 'Vent copy',
|
||||
front: 'Front copy',
|
||||
back: 'Back copy',
|
||||
quarter: 'Quarter copy',
|
||||
side: 'Side copy'
|
||||
},
|
||||
imageId: '00000000-0000-0000-0000-000000000000'
|
||||
},
|
||||
glassItems: {
|
||||
windshield: {
|
||||
glassLocation: damageLocationsSelected.WINDSHIELD,
|
||||
glassName: damageLocationsSelected.SINGLE
|
||||
},
|
||||
rear: {
|
||||
glassLocation: damageLocationsSelected.REAR,
|
||||
glassName: damageLocationsSelected.STATIONARY
|
||||
},
|
||||
passengerItems: {
|
||||
vent: {
|
||||
glassLocation: damageLocationsSelected.PASSENGER,
|
||||
glassName: damageLocationsSelected.VENT
|
||||
},
|
||||
front: {
|
||||
glassLocation: damageLocationsSelected.PASSENGER,
|
||||
glassName: damageLocationsSelected.FRONT
|
||||
},
|
||||
back: {
|
||||
glassLocation: damageLocationsSelected.PASSENGER,
|
||||
glassName: damageLocationsSelected.BACK
|
||||
},
|
||||
quarter: {
|
||||
glassLocation: damageLocationsSelected.PASSENGER,
|
||||
glassName: damageLocationsSelected.QUARTER
|
||||
},
|
||||
side: {
|
||||
glassLocation: damageLocationsSelected.PASSENGER,
|
||||
glassName: damageLocationsSelected.SIDEDOOR
|
||||
}
|
||||
},
|
||||
driverItems: {
|
||||
vent: {
|
||||
glassLocation: damageLocationsSelected.DRIVER,
|
||||
glassName: damageLocationsSelected.VENT
|
||||
},
|
||||
front: {
|
||||
glassLocation: damageLocationsSelected.DRIVER,
|
||||
glassName: damageLocationsSelected.FRONT
|
||||
},
|
||||
back: {
|
||||
glassLocation: damageLocationsSelected.DRIVER,
|
||||
glassName: damageLocationsSelected.BACK
|
||||
},
|
||||
quarter: {
|
||||
glassLocation: damageLocationsSelected.DRIVER,
|
||||
glassName: damageLocationsSelected.QUARTER
|
||||
},
|
||||
side: {
|
||||
glassLocation: damageLocationsSelected.DRIVER,
|
||||
glassName: damageLocationsSelected.SIDEDOOR
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const cmsContent = {
|
||||
DamageLocationsWidget: {
|
||||
Answers: [
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageLocations.windshield,
|
||||
Text: testConstants.cmsConstants.locationCopy.windshield,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageLocations.driver,
|
||||
Text: testConstants.cmsConstants.locationCopy.driver,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: testConstants.cmsConstants.widgetNames.driverDamages
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageLocations.passenger,
|
||||
Text: testConstants.cmsConstants.locationCopy.passenger,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: testConstants.cmsConstants.widgetNames.passengerDamages
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageLocations.rear,
|
||||
Text: testConstants.cmsConstants.locationCopy.rear,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
DriverDamagesWidget: {
|
||||
Answers: [
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.vent,
|
||||
Text: testConstants.cmsConstants.damageCopy.vent,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.front,
|
||||
Text: testConstants.cmsConstants.damageCopy.front,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.back,
|
||||
Text: testConstants.cmsConstants.damageCopy.back,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.quarter,
|
||||
Text: testConstants.cmsConstants.damageCopy.quarter,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.side,
|
||||
Text: testConstants.cmsConstants.damageCopy.side,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
PassengerDamagesWidget: {
|
||||
Answers: [
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.vent,
|
||||
Text: testConstants.cmsConstants.damageCopy.vent,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.front,
|
||||
Text: testConstants.cmsConstants.damageCopy.front,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.back,
|
||||
Text: testConstants.cmsConstants.damageCopy.back,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.quarter,
|
||||
Text: testConstants.cmsConstants.damageCopy.quarter,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
},
|
||||
{
|
||||
Name: testConstants.cmsConstants.damageNames.side,
|
||||
Text: testConstants.cmsConstants.damageCopy.side,
|
||||
SubText: '',
|
||||
ImageId: testConstants.cmsConstants.imageId,
|
||||
Image: '',
|
||||
SubWidgetName: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
describe('damage-review-content-generator', () => {
|
||||
describe('getLocationAnswer', () => {
|
||||
test('answer with damage location name match', () => {
|
||||
// Arrange
|
||||
const damageLocation = 'matching name';
|
||||
const expected = { Name: damageLocation };
|
||||
const locationAnswers = [{ Name: 'non matching name' }, expected];
|
||||
|
||||
// Act
|
||||
const result = getLocationAnswer(damageLocation, locationAnswers);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
test('no damage location name match', () => {
|
||||
// Arrange
|
||||
const damageLocation = 'random string';
|
||||
const locationAnswers = [{ Name: 'non matching name' }];
|
||||
|
||||
// Act
|
||||
const result = getLocationAnswer(damageLocation, locationAnswers);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
test.each([
|
||||
[[]],
|
||||
[null],
|
||||
[undefined]
|
||||
])('location answers %p', (locationAnswers) => {
|
||||
// Arrange
|
||||
const damageLocation = 'some string';
|
||||
|
||||
// Act
|
||||
const result = getLocationAnswer(damageLocation, locationAnswers);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
});
|
||||
describe('Correctly assembles damage info into a display string', () => {
|
||||
const locationAnswers = cmsContent.DamageLocationsWidget.Answers;
|
||||
test('Shows windshield copy when windshield damage is included', async () => {
|
||||
// Arrange
|
||||
const glassToReplace = [testConstants.glassItems.windshield];
|
||||
const isRepair = false;
|
||||
|
||||
// Act
|
||||
const result = getDamageDisplayContent(locationAnswers, null, null, glassToReplace, isRepair);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual([
|
||||
testConstants.cmsConstants.locationCopy.windshield
|
||||
]);
|
||||
});
|
||||
|
||||
test('Windshield copy is shown when order is a repair', async () => {
|
||||
// Arrange
|
||||
const glassToReplace = [];
|
||||
const isRepair = true;
|
||||
|
||||
// Act
|
||||
const result = getDamageDisplayContent(locationAnswers, null, null, glassToReplace, isRepair);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual([
|
||||
testConstants.cmsConstants.locationCopy.windshield
|
||||
]);
|
||||
});
|
||||
|
||||
test('Rear windshield copy shows when rear damage is present', async () => {
|
||||
// Arrange
|
||||
const glassToReplace = [testConstants.glassItems.rear];
|
||||
const isRepair = false;
|
||||
|
||||
// Act
|
||||
const result = getDamageDisplayContent(locationAnswers, null, null, glassToReplace, isRepair);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual([
|
||||
testConstants.cmsConstants.locationCopy.rear
|
||||
]);
|
||||
});
|
||||
|
||||
test('Driver side copy and items are shown when driver side damage is present', async () => {
|
||||
// Arrange
|
||||
const driverSideDamageAnswers = cmsContent.DriverDamagesWidget.Answers;
|
||||
const glassToReplace = [
|
||||
testConstants.glassItems.driverItems.back,
|
||||
testConstants.glassItems.driverItems.front
|
||||
];
|
||||
const isRepair = false;
|
||||
const expectedList = '<ul>'
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.front}</li>`
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.back}</li>`
|
||||
+ '</ul>';
|
||||
|
||||
// Act
|
||||
const result = getDamageDisplayContent(
|
||||
locationAnswers,
|
||||
driverSideDamageAnswers,
|
||||
null,
|
||||
glassToReplace,
|
||||
isRepair
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual([
|
||||
testConstants.cmsConstants.locationCopy.driver,
|
||||
expectedList
|
||||
]);
|
||||
});
|
||||
|
||||
test('Passenger side copy and items are shown when passenger side damage is present', async () => {
|
||||
// Arrange
|
||||
const passengerSideDamageAnswers = cmsContent.PassengerDamagesWidget.Answers;
|
||||
const glassToReplace = [
|
||||
testConstants.glassItems.passengerItems.quarter,
|
||||
testConstants.glassItems.passengerItems.vent,
|
||||
testConstants.glassItems.passengerItems.side
|
||||
];
|
||||
const isRepair = false;
|
||||
const expectedList = '<ul>'
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.vent}</li>`
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.quarter}</li>`
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.side}</li>`
|
||||
+ '</ul>';
|
||||
|
||||
// Act
|
||||
const result = getDamageDisplayContent(
|
||||
locationAnswers,
|
||||
null,
|
||||
passengerSideDamageAnswers,
|
||||
glassToReplace,
|
||||
isRepair
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual([
|
||||
testConstants.cmsConstants.locationCopy.passenger,
|
||||
expectedList
|
||||
]);
|
||||
});
|
||||
|
||||
test('All relevant sections are shown in order in multiglass scenario', async () => {
|
||||
// Arrange
|
||||
const driverSideDamageAnswers = cmsContent.DriverDamagesWidget.Answers;
|
||||
const passengerSideDamageAnswers = cmsContent.PassengerDamagesWidget.Answers;
|
||||
const glassToReplace = [
|
||||
testConstants.glassItems.windshield,
|
||||
testConstants.glassItems.rear,
|
||||
testConstants.glassItems.driverItems.vent,
|
||||
testConstants.glassItems.driverItems.front,
|
||||
testConstants.glassItems.driverItems.back,
|
||||
testConstants.glassItems.passengerItems.quarter,
|
||||
testConstants.glassItems.passengerItems.back,
|
||||
testConstants.glassItems.passengerItems.side
|
||||
];
|
||||
const isRepair = false;
|
||||
const expectedDriverDamageList = '<ul>'
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.vent}</li>`
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.front}</li>`
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.back}</li>`
|
||||
+ '</ul>';
|
||||
const expectedPassengerDamageList = '<ul>'
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.back}</li>`
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.quarter}</li>`
|
||||
+ `<li>${testConstants.cmsConstants.damageCopy.side}</li>`
|
||||
+ '</ul>';
|
||||
|
||||
// Act
|
||||
const result = getDamageDisplayContent(
|
||||
locationAnswers,
|
||||
driverSideDamageAnswers,
|
||||
passengerSideDamageAnswers,
|
||||
glassToReplace,
|
||||
isRepair
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual([
|
||||
testConstants.cmsConstants.locationCopy.windshield,
|
||||
testConstants.cmsConstants.locationCopy.driver,
|
||||
expectedDriverDamageList,
|
||||
testConstants.cmsConstants.locationCopy.passenger,
|
||||
expectedPassengerDamageList,
|
||||
testConstants.cmsConstants.locationCopy.rear
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { randomUUID } from 'crypto';
|
||||
|
||||
export function getRandomInt(min = 0, max = 1000) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min) + min); // The maximum is exclusive and the minimum is inclusive
|
||||
const minCeiling = Math.ceil(min);
|
||||
const maxFloor = Math.floor(max);
|
||||
return Math.floor(Math.random() * (maxFloor - minCeiling) + minCeiling); // The maximum is exclusive and the minimum is inclusive
|
||||
}
|
||||
|
||||
export function getRandomGuid() {
|
||||
|
|
|
|||
175
src/helpers/date-helper.js
Normal file
175
src/helpers/date-helper.js
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
export function calcDaysBetweenDates(dateString1, dateString2) {
|
||||
const date1 = new Date(dateString1);
|
||||
const date2 = new Date(dateString2);
|
||||
const timeDifference = Math.abs(date2 - date1); // Calculate the time difference in milliseconds
|
||||
return Math.ceil(timeDifference / (1000 * 60 * 60 * 24)); // Convert milliseconds to days
|
||||
}
|
||||
|
||||
export function convertDateToDateString(date) {
|
||||
// returns YYYY-MM-DD format
|
||||
if (date instanceof Date !== true) return null;
|
||||
return (
|
||||
`${date.getFullYear()
|
||||
}-${
|
||||
(`0${date.getMonth() + 1}`).slice(-2)
|
||||
}-${
|
||||
(`0${date.getDate()}`).slice(-2)}`
|
||||
);
|
||||
}
|
||||
|
||||
export function convertDateStringToDate(dateString) {
|
||||
// dateString must be YYYY-MM-DD format
|
||||
if (typeof dateString !== 'string') return null;
|
||||
const dateParts = dateString.split('-');
|
||||
return new Date(dateParts[0], parseInt(dateParts[1], 10) - 1, dateParts[2]);
|
||||
}
|
||||
|
||||
export function getDateDifferenceInDays(startDate, endDate) {
|
||||
const date1 = new Date(endDate);
|
||||
date1.setHours(0, 0, 0, 0);
|
||||
const date2 = new Date(startDate);
|
||||
date2.setHours(0, 0, 0, 0);
|
||||
// To calculate the time difference of two dates
|
||||
const DifferenceInTime = date1.getTime() - date2.getTime();
|
||||
// To calculate the no. of days between two dates
|
||||
return DifferenceInTime / (1000 * 3600 * 24);
|
||||
}
|
||||
|
||||
export function getDisplayTextForDurationLength(durationMinimum, durationMaximum) {
|
||||
const isLongAppointment = durationMaximum >= 120;
|
||||
const isDurationRange = durationMinimum !== durationMaximum;
|
||||
|
||||
const adjustedMinimum = isLongAppointment ? durationMinimum / 60 : durationMinimum;
|
||||
const adjustedMaximum = isLongAppointment ? durationMaximum / 60 : durationMaximum;
|
||||
|
||||
const durationText = isDurationRange
|
||||
? `${adjustedMinimum} - ${adjustedMaximum}`
|
||||
: adjustedMinimum;
|
||||
|
||||
const unitText = isLongAppointment ? 'hours' : 'minutes';
|
||||
|
||||
return `${durationText} ${unitText}`;
|
||||
}
|
||||
|
||||
export function militaryToTwelveHourTime(timeString) {
|
||||
// Expected input: "HH:MM"
|
||||
if (typeof timeString !== 'string') return null;
|
||||
let hours = parseInt(timeString.split(':')[0], 10);
|
||||
const minutes = timeString.split(':')[1];
|
||||
const meridianNotation = hours > 11 ? 'PM' : 'AM';
|
||||
|
||||
if (hours > 12) {
|
||||
hours -= 12;
|
||||
}
|
||||
|
||||
return `${hours}:${minutes} ${meridianNotation}`;
|
||||
}
|
||||
|
||||
export function sumDateString(dateString, daysToAdd) {
|
||||
// dateString must be YYYY-MM-DD format
|
||||
if (typeof dateString !== 'string') return null;
|
||||
const date = convertDateStringToDate(dateString);
|
||||
date.setDate(date.getDate() + daysToAdd);
|
||||
return convertDateToDateString(date);
|
||||
}
|
||||
|
||||
export function get12HourTimeMobileFormat(time) {
|
||||
// Check correct time format and split into components
|
||||
let timeString = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time];
|
||||
|
||||
if (timeString.length > 1) {
|
||||
// If time format correct
|
||||
const min = timeString[3];
|
||||
timeString = timeString.slice(1); // Remove full string match value
|
||||
if (Number(min) === 0) {
|
||||
timeString = timeString.slice(0, 1); // Remove minute value
|
||||
timeString[1] = +timeString[0] < 12 ? ' AM' : ' PM'; // Set AM/PM
|
||||
} else {
|
||||
timeString[5] = +timeString[0] < 12 ? ' AM' : ' PM'; // Set AM/PM
|
||||
}
|
||||
timeString[0] = +timeString[0] % 12 || 12; // Adjust hours
|
||||
}
|
||||
return timeString.join(''); // return adjusted time or original string
|
||||
}
|
||||
|
||||
export function get12HourTimeFormat(time) {
|
||||
// Check correct time format and split into components
|
||||
let timeString = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time];
|
||||
|
||||
if (timeString.length > 1) {
|
||||
// If time format correct
|
||||
timeString = timeString.slice(1); // Remove full string match value
|
||||
timeString[5] = +timeString[0] < 12 ? ' AM' : ' PM'; // Set AM/PM
|
||||
timeString[0] = +timeString[0] % 12 || 12; // Adjust hours
|
||||
}
|
||||
return timeString.join(''); // return adjusted time or original string
|
||||
}
|
||||
|
||||
export function getDateFormat(date, format) {
|
||||
if (date instanceof Date !== true) return;
|
||||
const year = date.getFullYear();
|
||||
const month = (`0${date.getMonth() + 1}`).slice(-2);
|
||||
const day = (`0${date.getDate()}`).slice(-2);
|
||||
const hour = (`0${date.getHours()}`).slice(-2);
|
||||
const minute = (`0${date.getMinutes()}`).slice(-2);
|
||||
const second = (`0${date.getSeconds()}`).slice(-2);
|
||||
// eslint-disable-next-line consistent-return
|
||||
return format
|
||||
.replace('yyyy', year)
|
||||
.replace('MM', month)
|
||||
.replace('dd', day)
|
||||
.replace('hh', hour)
|
||||
.replace('HH', hour)
|
||||
.replace('mm', minute)
|
||||
.replace('ss', second);
|
||||
}
|
||||
|
||||
export function padTo2Digits(time) {
|
||||
// Use the built-in method toString() with a radix of 10 to convert the time value to a decimal string
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
time = time.toString(10);
|
||||
// Use the conditional operator to check if the length of the string is less than 2
|
||||
return time.length < 2
|
||||
// If yes, prepend a '0' to the string and return it
|
||||
// If no, return the original string
|
||||
? `0${time}` : time;
|
||||
}
|
||||
|
||||
export function convertMsToTime(milliseconds) {
|
||||
let seconds = Math.floor(milliseconds / 1000);
|
||||
let minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
|
||||
seconds %= 60;
|
||||
minutes %= 60;
|
||||
// commenting to get 24 time format
|
||||
// hours = hours % 24;
|
||||
|
||||
return `${padTo2Digits(hours)}${padTo2Digits(minutes)}`;
|
||||
}
|
||||
|
||||
export function calculateDuration(startDate, endDate) {
|
||||
if (startDate instanceof Date !== true) return;
|
||||
if (endDate instanceof Date !== true) return;
|
||||
// eslint-disable-next-line consistent-return
|
||||
return convertMsToTime(endDate - startDate);
|
||||
}
|
||||
|
||||
export function combineDateAndTime(date, time) {
|
||||
// Use the Date.parse() method to convert the date and time strings to a numeric value
|
||||
const timestamp = Date.parse(`${date}T${time}`);
|
||||
// Use the new Date() constructor to create a new date object from the numeric value
|
||||
const newDate = new Date(timestamp);
|
||||
// Return the new date object
|
||||
return newDate;
|
||||
}
|
||||
export function addMinutes(date, minutes) {
|
||||
return new Date(date.getTime() + minutes * 60000);
|
||||
}
|
||||
export function shortTimeString(date) {
|
||||
// Use a ternary operator to check if the input is a valid date object
|
||||
return date instanceof Date
|
||||
// Use the built-in method toLocaleTimeString() to get the short time string in the current locale
|
||||
// Return undefined if the input is not a valid date object
|
||||
? date.toLocaleTimeString('en-us', { hour: 'numeric', minute: 'numeric', hour12: true }) : undefined;
|
||||
}
|
||||
|
|
@ -25,8 +25,11 @@ describe('event-bus.js', () => {
|
|||
it('removes items when readandpop is called', () => {
|
||||
useMainStore().eventBusItem.mockReturnValueOnce(event);
|
||||
|
||||
const eventValue = eventBus.readAndPopEventFromBus(globalEvents.Categories.GLOBAL_ALERT,
|
||||
globalEvents.SubCategories.PAGE_NOT_FOUND);
|
||||
// TODO: Use or remove
|
||||
const eventValue = eventBus.readAndPopEventFromBus(
|
||||
globalEvents.Categories.GLOBAL_ALERT,
|
||||
globalEvents.SubCategories.PAGE_NOT_FOUND
|
||||
);
|
||||
|
||||
expect(useMainStore().eventBusItem).toBeCalledTimes(1);
|
||||
expect(useMainStore().removeEventFromBus).toBeCalledTimes(1);
|
||||
|
|
@ -35,8 +38,11 @@ describe('event-bus.js', () => {
|
|||
it("doesn't try to remove items when readandpop is called and item doesn't exist", () => {
|
||||
useMainStore().eventBusItem.mockReturnValueOnce(undefined);
|
||||
|
||||
const eventValue = eventBus.readAndPopEventFromBus(globalEvents.Categories.GLOBAL_ALERT,
|
||||
globalEvents.SubCategories.PAGE_NOT_FOUND);
|
||||
// TODO: Use or remove
|
||||
const eventValue = eventBus.readAndPopEventFromBus(
|
||||
globalEvents.Categories.GLOBAL_ALERT,
|
||||
globalEvents.SubCategories.PAGE_NOT_FOUND
|
||||
);
|
||||
|
||||
expect(useMainStore().eventBusItem).toBeCalledTimes(1);
|
||||
expect(useMainStore().removeEventFromBus).toBeCalledTimes(0);
|
||||
|
|
@ -45,17 +51,21 @@ describe('event-bus.js', () => {
|
|||
it('returns event from bus', () => {
|
||||
useMainStore().eventBusItem.mockReturnValueOnce(event);
|
||||
|
||||
const eventValue = eventBus.readEventFromBus(globalEvents.Categories.GLOBAL_ALERT,
|
||||
globalEvents.SubCategories.PAGE_NOT_FOUND);
|
||||
const eventValue = eventBus.readEventFromBus(
|
||||
globalEvents.Categories.GLOBAL_ALERT,
|
||||
globalEvents.SubCategories.PAGE_NOT_FOUND
|
||||
);
|
||||
|
||||
expect(eventValue).toBe(event);
|
||||
});
|
||||
|
||||
it('Reads event from bus, should have event value.', () => {
|
||||
// Arrange / Act
|
||||
eventBus.addEventToBus(globalEvents.Categories.GLOBAL_ALERT,
|
||||
eventBus.addEventToBus(
|
||||
globalEvents.Categories.GLOBAL_ALERT,
|
||||
globalEvents.SubCategories.PAGE_NOT_FOUND,
|
||||
event);
|
||||
event
|
||||
);
|
||||
|
||||
expect(useMainStore().addEventToBus).toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { defineRule } from 'vee-validate';
|
||||
import { errorMessages } from '@/constants/error-messages';
|
||||
import errorMessages from '@/constants/error-messages';
|
||||
import globalRules from '@/constants/global-rules';
|
||||
import { required, regex } from '@/helpers/validation-rules';
|
||||
|
||||
|
|
@ -33,6 +33,39 @@ function defineGlobalEmailRules() {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Define global rules related to date/location of loss information
|
||||
*/
|
||||
function defineGlobalDateOfLossRules() {
|
||||
defineRule(globalRules.DATE_OF_LOSS_REQUIRED, required(errorMessages.LOSS_DATE_REQUIRED));
|
||||
defineRule(globalRules.DATE_OF_LOSS_NOT_TEN_YEARS_PAST, (value) => {
|
||||
const lossDate = new Date(Date.parse(`${value}T00:00:00`));
|
||||
const today = new Date();
|
||||
const tenYearsAgo = new Date(today.getFullYear() - 10, today.getMonth(), today.getDate(), 0, 0, 0, 0);
|
||||
if (lossDate < tenYearsAgo) {
|
||||
return errorMessages.DAMAGE_DATE_REQUIREMENT;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
defineRule(globalRules.DATE_OF_LOSS_NOT_FUTURE, (value) => {
|
||||
const lossDate = new Date(Date.parse(`${value}T00:00:00`));
|
||||
const today = new Date();
|
||||
const tomorrow = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1, 0, 0, 0, 0);
|
||||
if (lossDate >= tomorrow) {
|
||||
return errorMessages.DAMAGE_DATE_NO_FUTURE_DATE;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
defineRule(globalRules.DATE_OF_LOSS_CITY_REQUIRED, required(errorMessages.LOSS_CITY_REQUIRED));
|
||||
defineRule(
|
||||
globalRules.DATE_OF_LOSS_CITY_FORMAT,
|
||||
regex(/^[a-zA-Z .'-]+$/, errorMessages.LOSS_CITY_FORMAT)
|
||||
);
|
||||
defineRule(globalRules.DATE_OF_LOSS_STATE_REQUIRED, required(errorMessages.LOSS_STATE_REQUIRED));
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Define global rules related to phone numbers
|
||||
*/
|
||||
|
|
@ -45,22 +78,53 @@ function defineGlobalPhoneNumberRules() {
|
|||
errorMessages.PHONE_NUMBER_FORMAT
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Define global rules related to policy numbers
|
||||
*/
|
||||
function defineGlobalPolicyNumberRules() {
|
||||
defineRule(globalRules.POLICY_NUMBER_REQUIRED, required(errorMessages.POLICY_NUMBER_REQUIRED));
|
||||
defineRule(
|
||||
globalRules.PHONE_NUMBER_FORMAT_SHORT,
|
||||
globalRules.POLICY_NUMBER_FORMAT,
|
||||
regex(
|
||||
/^(\([0-9]{3}\)|[0-9]{3}) *[-.]? *[0-9]{3} *[-.]? *[0-9]{4}$/,
|
||||
errorMessages.PHONE_NUMBER_FORMAT_SHORT
|
||||
/^~?[a-zA-Z0-9]+$/,
|
||||
errorMessages.POLICY_NUMBER_FORMAT
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Define global rules related to policy zip codes
|
||||
*/
|
||||
function defineGlobalPolicyZipCodeRules() {
|
||||
defineRule(globalRules.POLICY_ZIP_REQUIRED, required(errorMessages.POLICY_ZIP_REQUIRED));
|
||||
defineRule(
|
||||
globalRules.POLICY_ZIP_FORMAT,
|
||||
regex(/^\d{5}$/, errorMessages.POLICY_ZIP_FORMAT)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Define global rules related to zip codes
|
||||
*/
|
||||
function defineGlobalZipCodeRules() {
|
||||
defineRule(globalRules.ZIP_CODE_REQUIRED, required(errorMessages.ZIP_REQUIRED));
|
||||
defineRule(globalRules.ZIP_CODE_SEARCH_FORMAT, regex(/^\d{5}$/, errorMessages.ZIP_FORMAT));
|
||||
}
|
||||
|
||||
/**
|
||||
* @function defineGlobalRules
|
||||
* @summary Define all global rules
|
||||
*/
|
||||
export default function defineGlobalRules() {
|
||||
defineGlobalNameRules();
|
||||
defineGlobalDateOfLossRules();
|
||||
defineGlobalEmailRules();
|
||||
defineGlobalPhoneNumberRules();
|
||||
defineGlobalPolicyNumberRules();
|
||||
defineGlobalPolicyZipCodeRules();
|
||||
defineGlobalZipCodeRules();
|
||||
|
||||
defineRule(globalRules.OPTION_REQUIRED, required(errorMessages.OPTION_REQUIRED));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export function settleAllPromises(promiseResultMap) {
|
||||
const settleAllPromises = (promiseResultMap) => {
|
||||
// Pull our keys out of the promise 'table'
|
||||
const promiseNames = Object.entries(promiseResultMap);
|
||||
|
||||
|
|
@ -22,4 +22,6 @@ export function settleAllPromises(promiseResultMap) {
|
|||
|
||||
return resultMap;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default settleAllPromises;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { settleAllPromises } from '@/helpers/layout-helper';
|
||||
import settleAllPromises from '@/helpers/layout-helper';
|
||||
|
||||
it('layout-helper: Should settle all promises and return mapped promise results', () => {
|
||||
// Arrange
|
||||
|
|
|
|||
20
src/helpers/loading-modal-helper.js
Normal file
20
src/helpers/loading-modal-helper.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { reactive } from 'vue';
|
||||
|
||||
// state encapsulated and managed by the composable
|
||||
const globalNonpersistedState = reactive({
|
||||
showLoadingModal: true,
|
||||
showTextCarousel: false
|
||||
});
|
||||
|
||||
const showIssLoadingModal = (newValue, showTextCarousel = false) => {
|
||||
if (newValue !== undefined) {
|
||||
globalNonpersistedState.showLoadingModal = newValue;
|
||||
}
|
||||
if (showTextCarousel !== undefined) {
|
||||
globalNonpersistedState.showTextCarousel = showTextCarousel;
|
||||
}
|
||||
|
||||
return { globalNonpersistedState };
|
||||
};
|
||||
|
||||
export default showIssLoadingModal;
|
||||
80
src/helpers/object-helper.js
Normal file
80
src/helpers/object-helper.js
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// For nested objects, spread operator only creates new references to the top level fields,
|
||||
// the remaining nested fields actually reference the original object which can introduce problems.
|
||||
|
||||
// The purpose of this method is to deep clone the data in an object recursively, this is useful
|
||||
// for cloning modelValues to internal models when regular two-way binding is not an option.
|
||||
// See: mobile-location-modal-questions.vue
|
||||
|
||||
// Creates a deep clone of an object. Clones primitives, arrays and objects, excluding class instances.
|
||||
// https://www.30secondsofcode.org/js/s/deep-clone
|
||||
export function deepClone(object) {
|
||||
if (object === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const clone = { ...object };
|
||||
// eslint-disable-next-line no-return-assign
|
||||
Object.keys(clone).forEach((key) =>
|
||||
(clone[key] = typeof object[key] === 'object' ? deepClone(object[key]) : object[key]));
|
||||
|
||||
if (Array.isArray(object)) {
|
||||
clone.length = object.length;
|
||||
return Array.from(clone);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
// The purpose of this method is to check for array or object equality recursively to determine if two complex objects are equal.
|
||||
// This is only a comparison of data, not functions.
|
||||
export function deepEqual(obj1, obj2) {
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj1 === null || obj2 === null) {
|
||||
return obj1 === obj2;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj1) && Array.isArray(obj2)) {
|
||||
if (obj1.length !== obj2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sorted1 = obj1.slice().sort();
|
||||
const sorted2 = obj2.slice().sort();
|
||||
|
||||
for (let i = 0; i < sorted1.length; i++) {
|
||||
if (!deepEqual(sorted1[i], sorted2[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof obj1 === 'object' && typeof obj2 === 'object') {
|
||||
const keys1 = Object.keys(obj1);
|
||||
const keys2 = Object.keys(obj2);
|
||||
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sortedKeys1 = keys1.sort();
|
||||
const sortedKeys2 = keys2.sort();
|
||||
|
||||
for (let i = 0; i < sortedKeys1.length; i++) {
|
||||
const key1 = sortedKeys1[i];
|
||||
const key2 = sortedKeys2[i];
|
||||
|
||||
if (key1 !== key2 || !deepEqual(obj1[key1], obj2[key2])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return obj1 === obj2;
|
||||
}
|
||||
48
src/helpers/order-helper.js
Normal file
48
src/helpers/order-helper.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { useMainStore } from '@/store';
|
||||
import { updateOrCreateISSCookie } from '@/helpers/cookie-helper';
|
||||
|
||||
/*
|
||||
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
|
||||
*/
|
||||
async function saveSessionHelper(store, { submitAfterSave }) {
|
||||
const savedSessionInfo = await store.saveSession({ submitAfterSave });
|
||||
if (savedSessionInfo) {
|
||||
store.setSaveSessionInfo(savedSessionInfo.data);
|
||||
}
|
||||
updateOrCreateISSCookie();
|
||||
}
|
||||
|
||||
/*
|
||||
Will call API to save existing order, or create new one depending where it's called from.
|
||||
This will also set Referral information in the store after saving, and then
|
||||
update the cookie. To force synchronous behavior pass in 'true' for shouldAwaitSaveSessionQueue
|
||||
*/
|
||||
export async function saveSession({ shouldAwaitSaveSessionQueue = false, submitAfterSave = false }) {
|
||||
const store = useMainStore();
|
||||
const saveSessionPromise = store.applicationUser.saveSessionPromise
|
||||
? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store, { submitAfterSave }))
|
||||
: saveSessionHelper(store, { submitAfterSave });
|
||||
|
||||
store.setSaveSessionPromise(saveSessionPromise);
|
||||
|
||||
if (!store.applicationUser.savedSessionId || shouldAwaitSaveSessionQueue) {
|
||||
await saveSessionPromise;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Will determine if to submitWorkOrder.
|
||||
TODO: Add more description to this
|
||||
*/
|
||||
export async function submitWorkOrder({
|
||||
pageNameToLog,
|
||||
submitAfterSave = false,
|
||||
createDeleteStatusWorkOrderForPia = false
|
||||
}) {
|
||||
await saveSession({
|
||||
pageNameToLog,
|
||||
shouldAwaitSaveSessionQueue: true,
|
||||
submitAfterSave,
|
||||
createDeleteStatusWorkOrderForPia
|
||||
});
|
||||
}
|
||||
23
src/helpers/policy-vehicle-helper.js
Normal file
23
src/helpers/policy-vehicle-helper.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import endorsementOptions from '@/constants/endorsement-options';
|
||||
|
||||
export function noCoverageForSelectedVehicle(vehicle) {
|
||||
return (vehicle?.coverages?.length ?? 0) === 0;
|
||||
}
|
||||
export function deductibleForSelectedVehicle(vehicle) {
|
||||
if (!vehicle) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return vehicle.coverages?.length ?? false
|
||||
? vehicle?.coverages[0].deductible
|
||||
: 0;
|
||||
}
|
||||
export function endorsementsForSelectedVehicle(vehicle) {
|
||||
if (vehicle?.endorsements?.length > 0) {
|
||||
return vehicle.endorsements;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
export function repairWaivedForSelectedVehicle(vehicle) {
|
||||
return vehicle?.endorsements?.includes(endorsementOptions.REPAIR_WAIVED) ?? false;
|
||||
}
|
||||
205
src/helpers/policy-vehicle-helper.spec.js
Normal file
205
src/helpers/policy-vehicle-helper.spec.js
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
import {
|
||||
deductibleForSelectedVehicle,
|
||||
endorsementsForSelectedVehicle,
|
||||
noCoverageForSelectedVehicle,
|
||||
repairWaivedForSelectedVehicle
|
||||
} from '@/helpers/policy-vehicle-helper';
|
||||
import { getRandomInt, getRandomString } from '@/helpers/data-generation';
|
||||
import endorsementOptions from '@/constants/endorsement-options';
|
||||
|
||||
describe('policy vehicle helper', () => {
|
||||
describe('noCoverageForSelectedVehicle function', () => {
|
||||
it('Null vehicle => returns true', async () => {
|
||||
// Arrange
|
||||
const vehicle = null;
|
||||
|
||||
// Act
|
||||
const result = noCoverageForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Empty vehicle coverages list => returns true', async () => {
|
||||
// Arrange
|
||||
const vehicle = {
|
||||
vin: getRandomString(17, 17),
|
||||
coverages: []
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = noCoverageForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Non-empty vehicle coverages list => returns false', async () => {
|
||||
// Arrange
|
||||
const vehicle = {
|
||||
vin: getRandomString(17, 17),
|
||||
coverages: [
|
||||
{
|
||||
deductible: 0
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = deductibleForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('deductibleForSelectedVehicle function', () => {
|
||||
it('Null vehicle => returns undefined', async () => {
|
||||
// Arrange
|
||||
const vehicle = null;
|
||||
|
||||
// Act
|
||||
const result = deductibleForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
|
||||
it('Vehicle match with empty coverages list => 0 returned', async () => {
|
||||
// Arrange
|
||||
const vehicle = {
|
||||
vin: getRandomString(17, 17),
|
||||
coverages: []
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = deductibleForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
it('Coverages list non empty => deductible from first coverage returned', async () => {
|
||||
// Arrange
|
||||
const firstDeductible = getRandomInt(1, 1000);
|
||||
const vehicle = {
|
||||
vin: getRandomString(17, 17),
|
||||
coverages: [
|
||||
{
|
||||
deductible: firstDeductible
|
||||
},
|
||||
{
|
||||
deductible: getRandomInt(1, 1000)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = deductibleForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(firstDeductible);
|
||||
});
|
||||
});
|
||||
|
||||
describe('repairWaivedForSelectedVehicle function', () => {
|
||||
it('Null vehicle => returns false', async () => {
|
||||
// Arrange
|
||||
const vehicle = null;
|
||||
|
||||
// Act
|
||||
const result = repairWaivedForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('Endorsements list empty => false returned', async () => {
|
||||
// Arrange
|
||||
const vehicle = {
|
||||
vin: getRandomString(17, 17),
|
||||
endorsements: []
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = repairWaivedForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('Endorsements list non-empty, not containing repair waived => false returned', async () => {
|
||||
// Arrange
|
||||
const vehicle = {
|
||||
vin: getRandomString(17, 17),
|
||||
endorsements: [endorsementOptions.EDUCATOR, endorsementOptions.PARKING_GUARD]
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = repairWaivedForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('Endorsements list contains repair waived => true returned', async () => {
|
||||
// Arrange
|
||||
const vehicle = {
|
||||
vin: getRandomString(17, 17),
|
||||
endorsements: [
|
||||
endorsementOptions.EDUCATOR,
|
||||
endorsementOptions.REPAIR_WAIVED,
|
||||
endorsementOptions.PARKING_GUARD
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = repairWaivedForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('endorsementsForSelectedVehicle function', () => {
|
||||
it('Null vehicle => returns empty array', async () => {
|
||||
// Arrange
|
||||
const vehicle = null;
|
||||
|
||||
// Act
|
||||
const result = endorsementsForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('Null endorsements => returns empty array', async () => {
|
||||
// Arrange
|
||||
const vehicle = {
|
||||
vin: getRandomString(17, 17),
|
||||
endorsements: null
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = endorsementsForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it('Endorsements list non-empty, not containing repair waived => false returned', async () => {
|
||||
// Arrange
|
||||
const endorsements = [endorsementOptions.EDUCATOR, endorsementOptions.PARKING_GUARD];
|
||||
const vehicle = {
|
||||
vin: getRandomString(17, 17),
|
||||
endorsements
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = endorsementsForSelectedVehicle(vehicle);
|
||||
|
||||
// Assert
|
||||
expect(result).toStrictEqual(endorsements);
|
||||
});
|
||||
});
|
||||
});
|
||||
12
src/helpers/price-calculator.js
Normal file
12
src/helpers/price-calculator.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
function getPriceOfLineItem(lineItem) {
|
||||
return (lineItem?.kitPrice ?? 0)
|
||||
+ (lineItem?.laborAmount ?? 0)
|
||||
+ (lineItem?.sellingPrice ?? 0);
|
||||
}
|
||||
|
||||
export default function getPriceOfLineItems(lineItems) {
|
||||
return lineItems.reduce(
|
||||
(accumulator, lineItem) => accumulator + getPriceOfLineItem(lineItem),
|
||||
0
|
||||
);
|
||||
}
|
||||
56
src/helpers/price-calculator.spec.js
Normal file
56
src/helpers/price-calculator.spec.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import getPriceOfLineItems from "@/helpers/price-calculator.js";
|
||||
|
||||
describe('getPriceOfLineItems', () => {
|
||||
test('Returns zero when no line items', () => {
|
||||
// Arrange
|
||||
const lineItems = [];
|
||||
|
||||
// Act
|
||||
const result = getPriceOfLineItems(lineItems);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
test('Returns expected when one line item', () => {
|
||||
// Arrange
|
||||
const lineItems = [
|
||||
{
|
||||
kitPrice: 1,
|
||||
laborAmount: 2,
|
||||
sellingPrice: 3
|
||||
}
|
||||
];
|
||||
const expected = 6;
|
||||
|
||||
// Act
|
||||
const result = getPriceOfLineItems(lineItems);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
test('Returns expected when multiple line items', () => {
|
||||
// Arrange
|
||||
const lineItems = [
|
||||
{
|
||||
kitPrice: 1,
|
||||
laborAmount: 2,
|
||||
sellingPrice: 3
|
||||
},
|
||||
{
|
||||
kitPrice: 1
|
||||
},
|
||||
{
|
||||
kitPrice: 10,
|
||||
laborAmount: 100,
|
||||
sellingPrice: 1000
|
||||
}
|
||||
];
|
||||
const expected = 1117;
|
||||
|
||||
// Act
|
||||
const result = getPriceOfLineItems(lineItems);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
11
src/helpers/querystring-helper.js
Normal file
11
src/helpers/querystring-helper.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export default function getQueryStringParameter(key) {
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
const lowerCaseParams = new URLSearchParams();
|
||||
|
||||
urlParams.forEach((value, name) => {
|
||||
lowerCaseParams.append(name.toLowerCase(), value);
|
||||
});
|
||||
|
||||
return lowerCaseParams.get(key.toLowerCase());
|
||||
}
|
||||
|
|
@ -1,14 +1,5 @@
|
|||
import { useMainStore } from '@/store';
|
||||
|
||||
export async function getServiceabilityDetails(serviceZipCode, lineItems) {
|
||||
const serviceabilityDetails = await useMainStore().getServiceabilityDetails({
|
||||
serviceZipCode,
|
||||
lineItems
|
||||
},
|
||||
false);
|
||||
return Promise.resolve(serviceabilityDetails);
|
||||
}
|
||||
|
||||
export async function getZipCodeData(zipCode) {
|
||||
const serviceZipValidationResponse = await useMainStore().validateZip({ zip: zipCode });
|
||||
|
||||
|
|
@ -20,3 +11,49 @@ export async function getZipCodeData(zipCode) {
|
|||
zipCodeCtu: serviceZipValidationResponse.data.zipCodeCtu
|
||||
};
|
||||
}
|
||||
|
||||
export async function getPricedMobileFeePart(serviceZipCode) {
|
||||
if (!serviceZipCode) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const zipCodeData = await getZipCodeData(serviceZipCode);
|
||||
|
||||
// Get the Mobile Fee Part
|
||||
const mobileFeePart = await useMainStore().getMobileFeePart();
|
||||
|
||||
// Get the Mobile Fee Part Price
|
||||
const pricingResults = await useMainStore()
|
||||
.priceOrderItemsAndSaveServerData([mobileFeePart.data], serviceZipCode, zipCodeData.zipCodeCtu);
|
||||
|
||||
return Promise.resolve(pricingResults[0]);
|
||||
}
|
||||
|
||||
export async function getServiceabilityDetails(serviceZipCode, lineItems) {
|
||||
const serviceabilityDetails = await useMainStore().getServiceabilityDetails(
|
||||
{
|
||||
serviceZipCode,
|
||||
lineItems
|
||||
},
|
||||
false
|
||||
);
|
||||
return Promise.resolve(serviceabilityDetails);
|
||||
}
|
||||
|
||||
export async function getAvailabilityRating(
|
||||
startDate,
|
||||
endDate,
|
||||
shopAppointmentType,
|
||||
providerNumber
|
||||
) {
|
||||
// For a given shop provider number and date range, get the appointment time slots available
|
||||
const shopTimeSlots = await useMainStore().getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber);
|
||||
|
||||
const numberOfDaysToEvaluate = 2;
|
||||
const isGoodAvailability =
|
||||
shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length
|
||||
>= numberOfDaysToEvaluate;
|
||||
|
||||
const shopStatus = isGoodAvailability ? 'high' : 'low';
|
||||
|
||||
return Promise.resolve(shopStatus);
|
||||
}
|
||||
|
|
|
|||
238
src/helpers/service-package-helper.js
Normal file
238
src/helpers/service-package-helper.js
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import partTypeStrings from '@/constants/part-type-strings';
|
||||
import damageLocationsSelected from '@/constants/damage-locations-selected';
|
||||
import packageNames from '@/constants/package-names';
|
||||
|
||||
export function findLineItemsWithPartType(typeToFind, itemsToSearch) {
|
||||
const partTypeMatches = itemsToSearch?.filter((lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase());
|
||||
return partTypeMatches;
|
||||
}
|
||||
|
||||
export function containsLineItemWithPartType(typeToFind, itemsToSearch) {
|
||||
const partTypeMatches = findLineItemsWithPartType(typeToFind, itemsToSearch);
|
||||
return !!partTypeMatches?.length;
|
||||
}
|
||||
|
||||
export function getVapsLineItems(availableLineItems, vapTypes) {
|
||||
const lineItems = [];
|
||||
|
||||
for (let i = 0; i < vapTypes.length; i++) {
|
||||
lineItems.push(...findLineItemsWithPartType(vapTypes[i], availableLineItems));
|
||||
}
|
||||
|
||||
return lineItems;
|
||||
}
|
||||
|
||||
export function containsGlassPieceWithLocation(locationToFind, glassPiecesToSearch) {
|
||||
const glassLocationMatches = glassPiecesToSearch?.filter((glassPiece) =>
|
||||
glassPiece.glassLocation.toUpperCase() === locationToFind.toUpperCase());
|
||||
|
||||
return !!glassLocationMatches?.length;
|
||||
}
|
||||
|
||||
export function shouldFrontWipersBeAvailable(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
targetTier
|
||||
) {
|
||||
const frontWiperIsAvailable = containsLineItemWithPartType(
|
||||
partTypeStrings.FRONT_WIPER,
|
||||
availableLineItems
|
||||
);
|
||||
|
||||
const isFrontWindshieldTask =
|
||||
isRepair || containsGlassPieceWithLocation(damageLocationsSelected.WINDSHIELD, glassToReplace);
|
||||
switch (targetTier) {
|
||||
case packageNames.TIER_TWO:
|
||||
return frontWiperIsAvailable && isFrontWindshieldTask;
|
||||
case packageNames.TIER_THREE:
|
||||
return frontWiperIsAvailable;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function shouldRearWipersBeAvailable(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
targetTier
|
||||
) {
|
||||
const rearWiperIsAvailable = containsLineItemWithPartType(
|
||||
partTypeStrings.REAR_WIPER,
|
||||
availableLineItems
|
||||
);
|
||||
const frontWiperIsAvailable = containsLineItemWithPartType(
|
||||
partTypeStrings.FRONT_WIPER,
|
||||
availableLineItems
|
||||
);
|
||||
const isRearWindshieldTask = containsGlassPieceWithLocation(
|
||||
damageLocationsSelected.REAR,
|
||||
glassToReplace
|
||||
);
|
||||
|
||||
switch (targetTier) {
|
||||
case packageNames.TIER_TWO:
|
||||
return rearWiperIsAvailable && isRearWindshieldTask;
|
||||
case packageNames.TIER_THREE:
|
||||
return rearWiperIsAvailable && (isRearWindshieldTask || !frontWiperIsAvailable);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function shouldRainDefenseBeAvailable(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
targetTier
|
||||
) {
|
||||
const isTierThree = targetTier === packageNames.TIER_THREE;
|
||||
const frontWipersInTierTwo = shouldFrontWipersBeAvailable(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
packageNames.TIER_TWO
|
||||
);
|
||||
const frontWipersInTierThree = shouldFrontWipersBeAvailable(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
packageNames.TIER_THREE
|
||||
);
|
||||
const rearWipersInTierTwo = shouldRearWipersBeAvailable(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
packageNames.TIER_TWO
|
||||
);
|
||||
|
||||
return isTierThree && !(rearWipersInTierTwo && !frontWipersInTierTwo && frontWipersInTierThree);
|
||||
}
|
||||
|
||||
export function getPackageContents(glassToReplace, availableLineItems, isRepair, targetTier) {
|
||||
const vaps = [];
|
||||
|
||||
if (shouldFrontWipersBeAvailable(glassToReplace, availableLineItems, isRepair, targetTier)) {
|
||||
vaps.push(partTypeStrings.FRONT_WIPER);
|
||||
}
|
||||
|
||||
if (shouldRearWipersBeAvailable(glassToReplace, availableLineItems, isRepair, targetTier)) {
|
||||
vaps.push(partTypeStrings.REAR_WIPER);
|
||||
}
|
||||
|
||||
if (shouldRainDefenseBeAvailable(glassToReplace, availableLineItems, isRepair, targetTier)) {
|
||||
vaps.push(partTypeStrings.RAIN_DEFENSE);
|
||||
}
|
||||
|
||||
return vaps;
|
||||
}
|
||||
|
||||
export function getAvailablePackages(glassToReplace, availableLineItems, isRepair) {
|
||||
const tierOneVaps = getPackageContents(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
packageNames.TIER_ONE
|
||||
);
|
||||
|
||||
const tierTwoVaps = getPackageContents(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
packageNames.TIER_TWO
|
||||
);
|
||||
|
||||
const tierThreeVaps = getPackageContents(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
packageNames.TIER_THREE
|
||||
);
|
||||
|
||||
if (tierTwoVaps.length === 0) {
|
||||
return [
|
||||
{
|
||||
packageName: packageNames.TIER_ONE,
|
||||
vapTypes: tierOneVaps
|
||||
},
|
||||
{
|
||||
packageName: packageNames.TIER_THREE,
|
||||
vapTypes: tierThreeVaps
|
||||
}
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
packageName: packageNames.TIER_ONE,
|
||||
vapTypes: tierOneVaps
|
||||
},
|
||||
{
|
||||
packageName: packageNames.TIER_TWO,
|
||||
vapTypes: tierTwoVaps
|
||||
},
|
||||
{
|
||||
packageName: packageNames.TIER_THREE,
|
||||
vapTypes: tierThreeVaps
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export function getLowestTierForType(glassToReplace, availableLineItems, isRepair, partType) {
|
||||
const packages = getAvailablePackages(glassToReplace, availableLineItems, isRepair);
|
||||
for (let i = 0; i < packages.length; i++) {
|
||||
for (let j = 0; j < packages[i].vapTypes.length; j++) {
|
||||
if (packages[i].vapTypes[j].toUpperCase() === partType.toUpperCase()) {
|
||||
return packages[i].packageName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packageNames.TIER_ONE;
|
||||
}
|
||||
|
||||
function maxTier(tierA, tierB) {
|
||||
if (tierA === packageNames.TIER_THREE || tierB === packageNames.TIER_THREE) {
|
||||
return packageNames.TIER_THREE;
|
||||
}
|
||||
|
||||
if (tierA === packageNames.TIER_TWO || tierB === packageNames.TIER_TWO) {
|
||||
return packageNames.TIER_TWO;
|
||||
}
|
||||
|
||||
return packageNames.TIER_ONE;
|
||||
}
|
||||
|
||||
export function getHighestRequiredTier(glassToReplace, availableLineItems, isRepair, vaps) {
|
||||
let currentHighestTier = packageNames.TIER_ONE;
|
||||
for (let i = 0; i < vaps.length; i++) {
|
||||
const lowestTierForItem = getLowestTierForType(
|
||||
glassToReplace,
|
||||
availableLineItems,
|
||||
isRepair,
|
||||
vaps[i].partType
|
||||
);
|
||||
|
||||
currentHighestTier = maxTier(currentHighestTier, lowestTierForItem);
|
||||
}
|
||||
|
||||
return currentHighestTier;
|
||||
}
|
||||
|
||||
export function getHighestFullySatisfiedTier(glassToReplace, availableLineItems, isRepair, vaps) {
|
||||
const packages = getAvailablePackages(glassToReplace, availableLineItems, isRepair);
|
||||
let highestSatisfiedPackage = packageNames.TIER_ONE;
|
||||
|
||||
for (let i = 0; i < packages.length; i++) {
|
||||
let isPackageSatisfied = true;
|
||||
for (let j = 0; j < packages[i].vapTypes.length; j++) {
|
||||
isPackageSatisfied =
|
||||
isPackageSatisfied && containsLineItemWithPartType(packages[i].vapTypes[j], vaps);
|
||||
}
|
||||
if (isPackageSatisfied) {
|
||||
highestSatisfiedPackage = packages[i].packageName;
|
||||
}
|
||||
}
|
||||
|
||||
return highestSatisfiedPackage;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { applicationConfig } from '@/constants/application-config';
|
||||
import applicationConfig from '@/constants/application-config';
|
||||
import { getISSCookie } from '@/helpers/cookie-helper.js';
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
isSavedSessionStillActive,
|
||||
getDateForSavedSessionTimeout
|
||||
} from '@/helpers/session-helper';
|
||||
import { applicationConfig } from '@/constants/application-config';
|
||||
import applicationConfig from '@/constants/application-config';
|
||||
|
||||
describe('isAnalyticsSessionStillActive', () => {
|
||||
test('isAnalyticsSessionStillActive, should return true', () => {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue