Merge branch 'develop' into feature/kiener/INSR-7753

This commit is contained in:
Katie Kroell 2026-01-22 15:36:53 -05:00
commit d0b970e68a
61 changed files with 2359 additions and 1615 deletions

199
package-lock.json generated
View file

@ -142,7 +142,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz",
"integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==", "integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.1.0", "@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6", "@babel/code-frame": "^7.18.6",
@ -2374,7 +2373,8 @@
"integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/@hapi/hoek": { "node_modules/@hapi/hoek": {
"version": "9.3.0", "version": "9.3.0",
@ -3281,6 +3281,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@gar/promisify": "^1.0.1", "@gar/promisify": "^1.0.1",
"semver": "^7.3.5" "semver": "^7.3.5"
@ -3293,6 +3294,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
}, },
@ -3308,6 +3310,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"rimraf": "^3.0.2" "rimraf": "^3.0.2"
@ -3323,6 +3326,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"bin": { "bin": {
"mkdirp": "bin/cmd.js" "mkdirp": "bin/cmd.js"
}, },
@ -3393,7 +3397,6 @@
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"playwright": "1.56.1" "playwright": "1.56.1"
}, },
@ -3973,7 +3976,6 @@
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.1.tgz", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.1.tgz",
"integrity": "sha512-P6iIPyYQ+qH8CvGauAqanhVnjrnRe0IZFSYCeGkSRW9q3u8bdVn2NPI+lasFyVsEQn1J/IFmp5Aax41+dAP9wg==", "integrity": "sha512-P6iIPyYQ+qH8CvGauAqanhVnjrnRe0IZFSYCeGkSRW9q3u8bdVn2NPI+lasFyVsEQn1J/IFmp5Aax41+dAP9wg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.10.4", "@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
@ -4440,7 +4442,6 @@
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@types/linkify-it": "*", "@types/linkify-it": "*",
"@types/mdurl": "*" "@types/mdurl": "*"
@ -4625,7 +4626,6 @@
"integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==", "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.23.0", "@typescript-eslint/scope-manager": "8.23.0",
"@typescript-eslint/types": "8.23.0", "@typescript-eslint/types": "8.23.0",
@ -5419,7 +5419,6 @@
"resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-5.0.8.tgz", "resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-5.0.8.tgz",
"integrity": "sha512-nV7tYQLe7YsTtzFrfOMIHc5N2hp5lHG2rpYr0aNja9rNljdgcPZLyQRb2YRivTHqTv7lI962UXFURcpStHgyFw==", "integrity": "sha512-nV7tYQLe7YsTtzFrfOMIHc5N2hp5lHG2rpYr0aNja9rNljdgcPZLyQRb2YRivTHqTv7lI962UXFURcpStHgyFw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@babel/helper-compilation-targets": "^7.12.16", "@babel/helper-compilation-targets": "^7.12.16",
"@soda/friendly-errors-webpack-plugin": "^1.8.0", "@soda/friendly-errors-webpack-plugin": "^1.8.0",
@ -5640,7 +5639,6 @@
"version": "3.3.4", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
"integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
"peer": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.20.15", "@babel/parser": "^7.20.15",
"@vue/compiler-core": "3.3.4", "@vue/compiler-core": "3.3.4",
@ -5775,7 +5773,6 @@
"version": "3.3.4", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
"integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
"peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.3.4", "@vue/compiler-ssr": "3.3.4",
"@vue/shared": "3.3.4" "@vue/shared": "3.3.4"
@ -5848,7 +5845,6 @@
"resolved": "https://registry.npmjs.org/@vue/vue3-jest/-/vue3-jest-27.0.0.tgz", "resolved": "https://registry.npmjs.org/@vue/vue3-jest/-/vue3-jest-27.0.0.tgz",
"integrity": "sha512-VL61CgZBoQqayXfzlZJHHpZuX4lsT8dmdZMJzADhdAJjKu26JBpypHr/2ppevxItljPiuALQW4MKhhCXZRXnLg==", "integrity": "sha512-VL61CgZBoQqayXfzlZJHHpZuX4lsT8dmdZMJzADhdAJjKu26JBpypHr/2ppevxItljPiuALQW4MKhhCXZRXnLg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.2.0", "@babel/plugin-transform-modules-commonjs": "^7.2.0",
"chalk": "^2.1.0", "chalk": "^2.1.0",
@ -6097,7 +6093,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
"integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
"dev": true, "dev": true,
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -6201,6 +6196,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"humanize-ms": "^1.2.1" "humanize-ms": "^1.2.1"
}, },
@ -6215,6 +6211,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"clean-stack": "^2.0.0", "clean-stack": "^2.0.0",
"indent-string": "^4.0.0" "indent-string": "^4.0.0"
@ -6228,7 +6225,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -6361,7 +6357,8 @@
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/arch": { "node_modules/arch": {
"version": "2.2.0", "version": "2.2.0",
@ -6400,6 +6397,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"delegates": "^1.0.0", "delegates": "^1.0.0",
"readable-stream": "^3.6.0" "readable-stream": "^3.6.0"
@ -6599,7 +6597,6 @@
"integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==", "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.4", "form-data": "^4.0.4",
@ -6663,7 +6660,6 @@
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
"integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@jest/transform": "^27.5.1", "@jest/transform": "^27.5.1",
"@jest/types": "^27.5.1", "@jest/types": "^27.5.1",
@ -6984,6 +6980,7 @@
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"file-uri-to-path": "1.0.0" "file-uri-to-path": "1.0.0"
} }
@ -7128,7 +7125,6 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"peer": true,
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001646", "caniuse-lite": "^1.0.30001646",
"electron-to-chromium": "^1.5.4", "electron-to-chromium": "^1.5.4",
@ -7217,6 +7213,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@npmcli/fs": "^1.0.0", "@npmcli/fs": "^1.0.0",
"@npmcli/move-file": "^1.0.1", "@npmcli/move-file": "^1.0.1",
@ -7248,6 +7245,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"bin": { "bin": {
"mkdirp": "bin/cmd.js" "mkdirp": "bin/cmd.js"
}, },
@ -7490,6 +7488,7 @@
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
@ -7534,6 +7533,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -7748,6 +7748,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"bin": { "bin": {
"color-support": "bin.js" "color-support": "bin.js"
} }
@ -8090,7 +8091,8 @@
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/consolidate": { "node_modules/consolidate": {
"version": "0.15.1", "version": "0.15.1",
@ -8292,7 +8294,6 @@
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
"integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==", "integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"icss-utils": "^5.1.0", "icss-utils": "^5.1.0",
"postcss": "^8.4.7", "postcss": "^8.4.7",
@ -8369,7 +8370,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0", "json-schema-traverse": "^1.0.0",
@ -8642,6 +8642,7 @@
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"mimic-response": "^3.1.0" "mimic-response": "^3.1.0"
}, },
@ -8706,6 +8707,7 @@
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=4.0.0" "node": ">=4.0.0"
} }
@ -8922,7 +8924,8 @@
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
@ -8949,6 +8952,7 @@
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -8969,10 +8973,11 @@
"dev": true "dev": true
}, },
"node_modules/diff": { "node_modules/diff": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
"dev": true, "dev": true,
"license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.3.1" "node": ">=0.3.1"
} }
@ -9125,7 +9130,6 @@
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
@ -9297,6 +9301,33 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
"node_modules/encoding/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/end-of-stream": { "node_modules/end-of-stream": {
"version": "1.4.4", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -9335,6 +9366,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -9345,7 +9377,8 @@
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/error-ex": { "node_modules/error-ex": {
"version": "1.3.2", "version": "1.3.2",
@ -9604,7 +9637,6 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1", "@eslint-community/regexpp": "^4.6.1",
@ -9736,7 +9768,6 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
"integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"array-includes": "^3.1.4", "array-includes": "^3.1.4",
"array.prototype.flat": "^1.2.5", "array.prototype.flat": "^1.2.5",
@ -10363,6 +10394,7 @@
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"dev": true, "dev": true,
"license": "(MIT OR WTFPL)", "license": "(MIT OR WTFPL)",
"peer": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -10621,7 +10653,8 @@
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
@ -10886,7 +10919,8 @@
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/fs-extra": { "node_modules/fs-extra": {
"version": "9.1.0", "version": "9.1.0",
@ -10909,6 +10943,7 @@
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peer": true,
"dependencies": { "dependencies": {
"minipass": "^3.0.0" "minipass": "^3.0.0"
}, },
@ -10986,6 +11021,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"aproba": "^1.0.3 || ^2.0.0", "aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.3", "color-support": "^1.1.3",
@ -11099,7 +11135,8 @@
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/glob": { "node_modules/glob": {
"version": "7.2.3", "version": "7.2.3",
@ -11291,7 +11328,8 @@
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/hash-sum": { "node_modules/hash-sum": {
"version": "2.0.0", "version": "2.0.0",
@ -11476,7 +11514,8 @@
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/http-deceiver": { "node_modules/http-deceiver": {
"version": "1.2.7", "version": "1.2.7",
@ -11587,6 +11626,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"ms": "^2.0.0" "ms": "^2.0.0"
} }
@ -11731,7 +11771,8 @@
"integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/inflight": { "node_modules/inflight": {
"version": "1.0.6", "version": "1.0.6",
@ -11776,6 +11817,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"jsbn": "1.1.0", "jsbn": "1.1.0",
"sprintf-js": "^1.1.3" "sprintf-js": "^1.1.3"
@ -11790,7 +11832,8 @@
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"dev": true, "dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "2.0.1", "version": "2.0.1",
@ -12044,7 +12087,8 @@
"integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/is-map": { "node_modules/is-map": {
"version": "2.0.2", "version": "2.0.2",
@ -12473,7 +12517,6 @@
"resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz",
"integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@jest/core": "^27.5.1", "@jest/core": "^27.5.1",
"import-local": "^3.0.2", "import-local": "^3.0.2",
@ -15187,7 +15230,8 @@
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/jsdoc": { "node_modules/jsdoc": {
"version": "4.0.2", "version": "4.0.2",
@ -15513,10 +15557,11 @@
} }
}, },
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/lodash.debounce": { "node_modules/lodash.debounce": {
"version": "4.0.8", "version": "4.0.8",
@ -15860,6 +15905,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"agentkeepalive": "^4.1.3", "agentkeepalive": "^4.1.3",
"cacache": "^15.2.0", "cacache": "^15.2.0",
@ -15889,6 +15935,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -15900,6 +15947,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@tootallnate/once": "1", "@tootallnate/once": "1",
"agent-base": "6", "agent-base": "6",
@ -16122,6 +16170,7 @@
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@ -16162,7 +16211,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0", "json-schema-traverse": "^1.0.0",
@ -16258,6 +16306,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"minipass": "^3.0.0" "minipass": "^3.0.0"
}, },
@ -16272,6 +16321,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"minipass": "^3.1.0", "minipass": "^3.1.0",
"minipass-sized": "^1.0.3", "minipass-sized": "^1.0.3",
@ -16291,6 +16341,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"minipass": "^3.0.0" "minipass": "^3.0.0"
}, },
@ -16305,6 +16356,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"minipass": "^3.0.0" "minipass": "^3.0.0"
}, },
@ -16319,6 +16371,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"minipass": "^3.0.0" "minipass": "^3.0.0"
}, },
@ -16332,6 +16385,7 @@
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"minipass": "^3.0.0", "minipass": "^3.0.0",
"yallist": "^4.0.0" "yallist": "^4.0.0"
@ -16357,7 +16411,8 @@
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/module-alias": { "node_modules/module-alias": {
"version": "2.2.2", "version": "2.2.2",
@ -16428,7 +16483,8 @@
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/natural-compare": { "node_modules/natural-compare": {
"version": "1.4.0", "version": "1.4.0",
@ -16473,6 +16529,7 @@
"integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"semver": "^7.3.5" "semver": "^7.3.5"
}, },
@ -16486,6 +16543,7 @@
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peer": true,
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
}, },
@ -16498,7 +16556,8 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.6.7", "version": "2.6.7",
@ -16559,6 +16618,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"env-paths": "^2.2.0", "env-paths": "^2.2.0",
"glob": "^7.1.4", "glob": "^7.1.4",
@ -16585,6 +16645,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"abbrev": "1" "abbrev": "1"
}, },
@ -16602,6 +16663,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
}, },
@ -16616,6 +16678,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
}, },
@ -16724,6 +16787,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"are-we-there-yet": "^3.0.0", "are-we-there-yet": "^3.0.0",
"console-control-strings": "^1.1.0", "console-control-strings": "^1.1.0",
@ -17077,6 +17141,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"aggregate-error": "^3.0.0" "aggregate-error": "^3.0.0"
}, },
@ -17313,7 +17378,6 @@
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.4.tgz", "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.4.tgz",
"integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==", "integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==",
"peer": true,
"dependencies": { "dependencies": {
"@vue/devtools-api": "^6.5.0", "@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5" "vue-demi": ">=0.14.5"
@ -17468,7 +17532,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@ -18001,6 +18064,7 @@
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"detect-libc": "^2.0.0", "detect-libc": "^2.0.0",
"expand-template": "^2.0.3", "expand-template": "^2.0.3",
@ -18116,7 +18180,8 @@
"integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/promise-retry": { "node_modules/promise-retry": {
"version": "2.0.1", "version": "2.0.1",
@ -18125,6 +18190,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"err-code": "^2.0.2", "err-code": "^2.0.2",
"retry": "^0.12.0" "retry": "^0.12.0"
@ -18140,6 +18206,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">= 4" "node": ">= 4"
} }
@ -18319,6 +18386,7 @@
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dev": true, "dev": true,
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"peer": true,
"dependencies": { "dependencies": {
"deep-extend": "^0.6.0", "deep-extend": "^0.6.0",
"ini": "~1.3.0", "ini": "~1.3.0",
@ -18335,6 +18403,7 @@
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -18783,7 +18852,6 @@
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz",
"integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0", "immutable": "^4.0.0",
@ -19064,7 +19132,8 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true "optional": true,
"peer": true
}, },
"node_modules/set-function-length": { "node_modules/set-function-length": {
"version": "1.2.2", "version": "1.2.2",
@ -19252,7 +19321,8 @@
"url": "https://feross.org/support" "url": "https://feross.org/support"
} }
], ],
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/simple-get": { "node_modules/simple-get": {
"version": "4.0.1", "version": "4.0.1",
@ -19274,6 +19344,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"decompress-response": "^6.0.0", "decompress-response": "^6.0.0",
"once": "^1.3.1", "once": "^1.3.1",
@ -19316,6 +19387,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"engines": { "engines": {
"node": ">= 6.0.0", "node": ">= 6.0.0",
"npm": ">= 3.0.0" "npm": ">= 3.0.0"
@ -19348,6 +19420,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"ip-address": "^9.0.5", "ip-address": "^9.0.5",
"smart-buffer": "^4.2.0" "smart-buffer": "^4.2.0"
@ -19364,6 +19437,7 @@
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"agent-base": "^6.0.2", "agent-base": "^6.0.2",
"debug": "^4.3.3", "debug": "^4.3.3",
@ -19912,6 +19986,7 @@
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peer": true,
"dependencies": { "dependencies": {
"chownr": "^2.0.0", "chownr": "^2.0.0",
"fs-minipass": "^2.0.0", "fs-minipass": "^2.0.0",
@ -19930,6 +20005,7 @@
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"chownr": "^1.1.1", "chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2", "mkdirp-classic": "^0.5.2",
@ -19942,7 +20018,8 @@
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC",
"peer": true
}, },
"node_modules/tar-fs/node_modules/tar-stream": { "node_modules/tar-fs/node_modules/tar-stream": {
"version": "2.2.0", "version": "2.2.0",
@ -19950,6 +20027,7 @@
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"bl": "^4.0.3", "bl": "^4.0.3",
"end-of-stream": "^1.4.1", "end-of-stream": "^1.4.1",
@ -19979,6 +20057,7 @@
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -19989,6 +20068,7 @@
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"mkdirp": "bin/cmd.js" "mkdirp": "bin/cmd.js"
}, },
@ -20225,7 +20305,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -20503,6 +20582,7 @@
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
}, },
@ -20558,7 +20638,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"devOptional": true, "devOptional": true,
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -20698,6 +20777,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"unique-slug": "^2.0.0" "unique-slug": "^2.0.0"
} }
@ -20709,6 +20789,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"imurmurhash": "^0.1.4" "imurmurhash": "^0.1.4"
} }
@ -20872,7 +20953,6 @@
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.4", "fdir": "^6.4.4",
@ -21004,7 +21084,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -21018,7 +21097,6 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/chai": "^5.2.2", "@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4", "@vitest/expect": "3.2.4",
@ -21171,7 +21249,6 @@
"version": "3.3.4", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
"integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
"peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.3.4", "@vue/compiler-dom": "3.3.4",
"@vue/compiler-sfc": "3.3.4", "@vue/compiler-sfc": "3.3.4",
@ -21482,7 +21559,6 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@types/estree": "^1.0.5", "@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/ast": "^1.12.1",
@ -21698,7 +21774,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0", "json-schema-traverse": "^1.0.0",
@ -21807,7 +21882,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0", "json-schema-traverse": "^1.0.0",
@ -21874,7 +21948,6 @@
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=10.13.0"
} }
@ -22047,6 +22120,7 @@
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"optional": true, "optional": true,
"peer": true,
"dependencies": { "dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4" "string-width": "^1.0.2 || 2 || 3 || 4"
} }
@ -22182,7 +22256,6 @@
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="21" viewBox="0 0 20 21">
<path fill="#E86421" fill-rule="nonzero" d="M10 .5c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10S4.477.5 10 .5zm.043 13.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm.77-9.2h-1.54l-.088.009a.502.502 0 0 0-.299.193.66.66 0 0 0-.125.47l.747 6.806.017.096c.065.249.263.425.495.426l.084-.008c.22-.042.396-.246.427-.51l.793-6.805.004-.102a.651.651 0 0 0-.127-.37.489.489 0 0 0-.388-.205z"/>
</svg>

After

Width:  |  Height:  |  Size: 474 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="9" viewBox="0 0 15 9">
<path fill="#E86421" fill-rule="nonzero" d="M7.5 0a.806.806 0 0 0-.593.265L.246 7.455a.957.957 0 0 0 0 1.28.796.796 0 0 0 1.185 0l6.07-6.55 6.068 6.55a.796.796 0 0 0 1.186 0 .957.957 0 0 0 0-1.28L8.093.265A.806.806 0 0 0 7.5 0"/>
</svg>

After

Width:  |  Height:  |  Size: 323 B

View file

@ -1,9 +0,0 @@
const buttonVariants = Object.freeze({
primary: 'primary',
secondary: 'secondary',
success: 'success',
link: 'link',
navigation: 'navigation'
});
export default buttonVariants;

View file

@ -0,0 +1,15 @@
export const buttonVariants = Object.freeze({
primary: 'primary',
secondary: 'secondary',
success: 'success',
link: 'link',
navigation: 'navigation'
});
export const dropdownVariants = Object.freeze({
primary: 'primary',
compact: 'compact'
});
export const modalPositions = Object.freeze({
center: 'center',
edge: 'edge'
});

View file

@ -23,8 +23,13 @@ const errorMessages = Object.freeze({
LAST_NAME_REQUIRED: 'Please enter your last name', LAST_NAME_REQUIRED: 'Please enter your last name',
EMAIL_ADDRESS_REQUIRED: 'Email is required.', EMAIL_ADDRESS_REQUIRED: 'Email is required.',
EMAIL_ADDRESS_FORMAT: 'Please enter a valid email address.', EMAIL_ADDRESS_FORMAT: 'Please enter a valid email address.',
SERVICE_ZIP_REQUIRED: 'Please enter your service ZIP', SERVICE_ZIP_REQUIRED: 'ZIP code is required',
SERVICE_ZIP_FORMAT: 'Please enter a valid service ZIP', SERVICE_ZIP_FORMAT: 'Invalid ZIP, please enter a new ZIP (5 characters)',
MOBILE_SERVICE_ZIP_FORMAT: 'Invalid ZIP code',
NO_SERVICE_IN_AREA: (area) => `We're sorry, but we don't offer service in ${area} at this time.`,
PROVIDER_REQUIRED: 'Please select a provider.',
INVALID_ZIP: 'The ZIP you entered was invalid. Please enter a valid ZIP.',
ZIP_CODE_NOT_SERVICED_FOR_VEHICLE: 'We do not currently offer glass service for your vehicle in this ZIP code. Please try another ZIP code.',
VIN_REQUIRED: 'Please enter your VIN', VIN_REQUIRED: 'Please enter your VIN',
VIN_FORMAT: VIN_FORMAT:
// eslint-disable-next-line max-len // eslint-disable-next-line max-len

View file

@ -71,6 +71,9 @@ export const pageProgressMapper = {
'tpa-search': { 'tpa-search': {
percent: 85 percent: 85
}, },
'tpa-submit': {
percent: 95
},
'tpa-confirmation': { 'tpa-confirmation': {
percent: 100 percent: 100
}, },

View file

@ -294,6 +294,11 @@ export default {
this.resetField({ this.resetField({
value: newValue value: newValue
}); });
},
answers() {
this.resetField({
value: this.modelValue
});
} }
}, },
beforeMount() { beforeMount() {

View file

@ -2,8 +2,9 @@
<div <div
class="dropdown-question" class="dropdown-question"
:class="{ :class="{
'has-error' : (meta.touched && errors && errors.length && !isDisabled) || hasError, 'has-error' : (meta?.touched && errors && errors.length && !isDisabled) || hasError,
'has-success': (meta.touched && (!errors || !errors.length) && !isDisabled) 'has-success': (meta?.touched && (!errors || !errors.length) && !isDisabled),
[`dropdown-${variant}`]: true
}"> }">
<label <label
:for="inputId" :for="inputId"
@ -34,7 +35,7 @@
</option> </option>
</select> </select>
<div <div
v-show="meta.touched && errorMessage && !isDisabled" v-show="meta?.touched && errorMessage && !isDisabled"
class="row mt-1 form-test-error"> class="row mt-1 form-test-error">
<span role="alert">{{ errorMessage }}</span> <span role="alert">{{ errorMessage }}</span>
</div> </div>
@ -42,6 +43,7 @@
</template> </template>
<script> <script>
import { dropdownVariants } from '@/constants/component-variants';
import { useField } from 'vee-validate'; import { useField } from 'vee-validate';
export default { export default {
@ -59,7 +61,11 @@ export default {
validationRules: String, validationRules: String,
cmsWidgetName: String, cmsWidgetName: String,
hasError: Boolean, hasError: Boolean,
placeHolderText: String placeHolderText: String,
variant: {
type: String,
default: dropdownVariants.primary
}
}, },
emits: ['update:modelValue'], emits: ['update:modelValue'],
setup(props) { setup(props) {
@ -154,5 +160,30 @@ export default {
filter: grayscale(100%); filter: grayscale(100%);
} }
} }
&.dropdown-compact {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 1.25rem;
.form-label {
color: $darker-gray;
font-weight: $font-weight-light;
margin: 0;
}
.form-select {
color: $heritage-blue-secondary;
background-position-x: 98%;
padding: 0;
padding-left: .3125rem;
width: 5.625rem;
min-height: auto;
border: none;
box-shadow: none;
&:focus,
&:focus-visible {
border: .125rem solid $black;
}
}
}
} }
</style> </style>

View file

@ -13,19 +13,19 @@
}"> }">
<div class="modal-dialog" <div class="modal-dialog"
:class="{ :class="{
'modal-dialog-edge': modalPosition === 'edge', 'modal-dialog-edge': modalPosition === modalPositions.edge,
'modal-dialog-centered': modalPosition === 'center' 'modal-dialog-centered': modalPosition === modalPositions.center
}"> }">
<div <div
class="modal-content" class="modal-content"
role="dialog" role="dialog"
aria-modal="true"> aria-modal="true">
<div class="modal-header mb-2 mt-2"> <div class="modal-header mb-2 mt-2">
<label <span
v-if="headerText" v-if="headerText"
class="modal-title d-flex justify-content-center pb-0 w-100"> class="modal-title d-flex justify-content-center pb-0 w-100"
{{ headerText }} :innerHTML="headerText">
</label> </span>
<button <button
ref="closeButton" ref="closeButton"
type="button" type="button"
@ -60,6 +60,7 @@
import ButtonMain from '@/ux-components/button-main/button-main.vue'; import ButtonMain from '@/ux-components/button-main/button-main.vue';
import { Modal } from 'bootstrap'; import { Modal } from 'bootstrap';
import { useForm } from 'vee-validate'; import { useForm } from 'vee-validate';
import { modalPositions } from '@/constants/component-variants';
export default { export default {
// eslint-disable-next-line vue/multi-word-component-names // eslint-disable-next-line vue/multi-word-component-names
@ -84,7 +85,11 @@ export default {
}, },
modalPosition: { modalPosition: {
type: String, type: String,
default: 'edge' default: modalPositions.edge
},
allowInvalidSubmit: {
type: Boolean,
default: false
} }
}, },
emits: ['footer-button-event', 'isModalOpened'], emits: ['footer-button-event', 'isModalOpened'],
@ -102,7 +107,8 @@ export default {
modalId, modalId,
meta, meta,
validate, validate,
resetForm resetForm,
modalPositions
}; };
}, },
computed: { computed: {
@ -121,7 +127,7 @@ export default {
methods: { methods: {
async validateAndEmit() { async validateAndEmit() {
const validationResult = await this.validate(); const validationResult = await this.validate();
if (validationResult.valid) { if (validationResult.valid || this.allowInvalidSubmit) {
this.$emit('footer-button-event'); this.$emit('footer-button-event');
} }
}, },
@ -257,6 +263,9 @@ export default {
border-radius: 1.5rem; border-radius: 1.5rem;
overflow: auto; overflow: auto;
} }
.modal-header {
justify-content: center;
}
} }
} }
&.show .modal-dialog { &.show .modal-dialog {

View file

@ -51,7 +51,7 @@ export default {
props: { props: {
customText: String, // used to allow the insert of token values into textblock customText: String, // used to allow the insert of token values into textblock
justifyText: String, // left, right, center justifyText: String, // left, right, center
typeStyle: String, // h1-h6, body, small, label, caption typeStyle: String, // h1-h6, body, small, label, caption, disclaimer
// (see Figma or Confluence documentation) // (see Figma or Confluence documentation)
fontWeight: String, // bold=500, default is 400 fontWeight: String, // bold=500, default is 400
cmsWidgetName: String, cmsWidgetName: String,
@ -109,5 +109,12 @@ export default {
&.dark { &.dark {
color: $black; color: $black;
} }
&.disclaimer {
color: $lighter-gray;
font-weight: $font-weight-light;
font-size: .8125rem;
font-style: italic;
margin-top: .625rem;
}
} }
</style> </style>

View file

@ -16,7 +16,8 @@
class="input-wrapper" class="input-wrapper"
:class="[ :class="[
includeSearchIcon ? 'has-search-icon' : '', includeSearchIcon ? 'has-search-icon' : '',
includeSelectIcon ? 'has-select-icon' : '' includeSelectIcon ? 'has-select-icon' : '',
hasTextButton ? 'has-text-button' : ''
]"> ]">
<input <input
:id="inputId" :id="inputId"
@ -61,6 +62,13 @@
data-bs-toggle="modal" data-bs-toggle="modal"
:data-bs-target="'#' + cmsWidgetName" :data-bs-target="'#' + cmsWidgetName"
aria-label="Select button" /> aria-label="Select button" />
<buttonMain
v-if="hasTextButton"
ref="buttonMain"
variant="primary"
:buttonText="buttonText"
@clickEvent="clickedTextButton">
</buttonMain>
</div> </div>
<div <div
v-show="errorMessage" v-show="errorMessage"
@ -76,9 +84,13 @@
<script> <script>
import { useField, validate } from 'vee-validate'; import { useField, validate } from 'vee-validate';
import { extractSpanText } from '@/helpers/text-helper'; import { extractSpanText } from '@/helpers/text-helper';
import buttonMain from '@/ux-components/button-main/button-main.vue';
export default { export default {
name: 'textbox-question', name: 'textbox-question',
components: {
buttonMain
},
props: { props: {
type: { type: {
type: String, type: String,
@ -118,7 +130,11 @@ export default {
includeSelectIcon: Boolean, includeSelectIcon: Boolean,
min: String, min: String,
max: String, max: String,
disableAutoFill: Boolean disableAutoFill: Boolean,
buttonCallback: {
type: Function,
default: () => {}
}
}, },
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'], emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'],
setup(props) { setup(props) {
@ -161,6 +177,12 @@ export default {
questionTextForAria() { questionTextForAria() {
return extractSpanText(this.getCmsContent(this.cmsWidgetName, 'QuestionText')); return extractSpanText(this.getCmsContent(this.cmsWidgetName, 'QuestionText'));
}, },
buttonText() {
return this.getCmsContent(this.cmsWidgetName, 'ButtonText');
},
hasTextButton() {
return this.buttonText && this.buttonText.length > 0;
},
value: { value: {
get() { get() {
return this.modelValue; return this.modelValue;
@ -227,6 +249,13 @@ export default {
if (this.meta.valid) { if (this.meta.valid) {
this.$emit('click-event'); this.$emit('click-event');
} }
},
async clickedTextButton() {
const result = await validate(this.value, this.validationRules);
if (result.valid) {
this.handleChange(this.value);
this.$emit('click-event');
}
} }
} }
}; };
@ -273,7 +302,7 @@ input::-webkit-date-and-time-value {
color: #4d5151; color: #4d5151;
} }
.form-test-error span { .form-test-error span {
color: #d4281c; color: $red;
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
} }
@ -318,6 +347,19 @@ input::-webkit-date-and-time-value {
display: flex; display: flex;
} }
} }
&.has-text-button {
display: flex;
input[type='text'] {
border-radius: 1.5rem;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
min-width: 6.875rem;
}
}
} }
input { input {
&.has-icon { &.has-icon {
@ -338,8 +380,8 @@ input::-webkit-date-and-time-value {
.form-control { .form-control {
border: $border-input; border: $border-input;
border-radius: $border-radius; border-radius: $border-radius;
min-height: 3rem; min-height: 2.8125rem;
max-height: 3rem; max-height: 2.8125rem;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
letter-spacing: inherit; letter-spacing: inherit;
box-shadow: $box-shadow-input; box-shadow: $box-shadow-input;

View file

@ -5,6 +5,7 @@ function processZipCodeResults(request) {
containsMilitaryBase: serviceZipValidationResponse.containsMilitaryBase, containsMilitaryBase: serviceZipValidationResponse.containsMilitaryBase,
isValid: serviceZipValidationResponse.isValid, isValid: serviceZipValidationResponse.isValid,
isServiceable: serviceZipValidationResponse.isServiceable, isServiceable: serviceZipValidationResponse.isServiceable,
city: serviceZipValidationResponse.city,
state: serviceZipValidationResponse.state, state: serviceZipValidationResponse.state,
zipCodeCtu: serviceZipValidationResponse.zipCodeCtu zipCodeCtu: serviceZipValidationResponse.zipCodeCtu
})).catch(() => ({ })).catch(() => ({

View file

@ -45,11 +45,8 @@ export function createUnorderedListFromStringOfParagraphs(stringOfParagraphs) {
* @returns {string} * @returns {string}
*/ */
export function toTitleCase(text) { export function toTitleCase(text) {
const temp = text?.toLowerCase()?.split(' ') ?? []; let temp = text?.toLowerCase() ?? '';
for (let i = 0; i < temp.length; i++) { return temp.replace(/(^|\s|-)\S/g, (letter) => letter.toUpperCase());
temp[i] = temp[i].charAt(0).toUpperCase() + temp[i].slice(1);
}
return temp.join(' ');
} }
/** /**

View file

@ -314,7 +314,6 @@ describe('Google Map', () => {
// Arrange // Arrange
const wrapper = shallowMount(googleMap, {}); const wrapper = shallowMount(googleMap, {});
await awaitingSetupTicks(wrapper); await awaitingSetupTicks(wrapper);
const numberOfExtendCallsDuringMount = 2;
mockGeocode.mockImplementation(() => { mockGeocode.mockImplementation(() => {
const result = { const result = {
@ -329,7 +328,7 @@ describe('Google Map', () => {
const result = wrapper.vm.getBounds(locations); const result = wrapper.vm.getBounds(locations);
// Assert // Assert
expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCallsDuringMount + numberOfExtendCallsPostMount); expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCallsPostMount);
expect(result).toBe(mockLatLngBound); expect(result).toBe(mockLatLngBound);
} }
); );

View file

@ -55,7 +55,7 @@ export default {
const { AdvancedMarkerElement, PinElement } = await window.google.maps.importLibrary('marker'); const { AdvancedMarkerElement, PinElement } = await window.google.maps.importLibrary('marker');
markers?.forEach((marker, index) => { markers?.forEach((marker, index) => {
const pinElement = new PinElement({ const pinElement = new PinElement({
glyph: String.fromCharCode('A'.charCodeAt(0) + index), glyphText: String.fromCharCode('A'.charCodeAt(0) + index),
glyphColor: '#000000', glyphColor: '#000000',
borderColor: '#000000' borderColor: '#000000'
}); });
@ -107,9 +107,11 @@ export default {
async getBoundsFromAddress(address) { async getBoundsFromAddress(address) {
await this.setGeocoder(); await this.setGeocoder();
const result = await this.geocoder.geocode({ address }); const result = await this.geocoder.geocode({ address });
return result?.results?.length > 0 if (result?.results?.length > 0) {
? result.results[0].geometry?.bounds return result.results[0].geometry?.bounds ?? null;
: null; } else {
return null;
}
}, },
async createMapWithMarkersForAddresses(markers) { async createMapWithMarkersForAddresses(markers) {
const markerPositions = await this.getLocationsFromAddresses(markers); const markerPositions = await this.getLocationsFromAddresses(markers);
@ -117,9 +119,12 @@ export default {
const map = await this.getMap(zipBounds?.getCenter()); const map = await this.getMap(zipBounds?.getCenter());
await this.addMarkersToMap(map, markerPositions); await this.addMarkersToMap(map, markerPositions);
let positionsToDisplay = markerPositions.map((marker) => marker.position);
if(zipBounds) {
positionsToDisplay.push(zipBounds.getNorthEast());
positionsToDisplay.push(zipBounds.getSouthWest());
}
const positionsToDisplay = markerPositions.map((marker) => marker.position)
.concat(zipBounds?.getNorthEast(), zipBounds?.getSouthWest());
const bounds = this.getBounds(positionsToDisplay); const bounds = this.getBounds(positionsToDisplay);
map.fitBounds(bounds); map.fitBounds(bounds);
} }

View file

@ -2,13 +2,13 @@
exports[`Shop list button should render correctly with all relevant props 1`] = ` exports[`Shop list button should render correctly with all relevant props 1`] = `
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true" selectedvalue="selected value"> <transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true" selectedvalue="selected value">
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" buttonlabel="Label of button" buttonlabelsubcopy="Sub copy of button" buttonbodycopy="button body copy" screenreaderonlytext="screen reader only text" additionalbuttondata="[object Object]" alttext="" iswide="false" value="1234"></base-input-button-stub> <base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" buttonlabel="Label of button" buttonlabelsubcopy="Sub copy of button" buttonbodycopy="button body copy" screenreaderonlytext="screen reader only text" additionalbuttondata="[object Object]" alttext="" iswide="false" value="1234"></base-input-button-stub>
</transition-stub> </transition-stub>
`; `;
exports[`Shop list button should render correctly with required props 1`] = ` exports[`Shop list button should render correctly with required props 1`] = `
<transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true"> <transition-stub name="fade" mode="out-in" appear="false" persisted="false" css="true">
<base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" alttext="" iswide="false" value="1234"></base-input-button-stub> <base-input-button-stub modelvalue="value of modal" groupname="name of group" buttonwrapperclasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover mb-2" ismultiselect="false" validationrules="" isrequired="true" selectinginitiatesload="false" suppresserror="false" alttext="" iswide="false" value="1234"></base-input-button-stub>
</transition-stub> </transition-stub>
`; `;

View file

@ -149,10 +149,9 @@ describe('Shop list button', () => {
// Assert // Assert
expect(buttonLabelSubCopy.exists()).toBeTruthy(); expect(buttonLabelSubCopy.exists()).toBeTruthy();
expect(buttonLabelSubCopy.classes().length).toBe(4); expect(buttonLabelSubCopy.classes().length).toBe(3);
expect(buttonLabelSubCopy.classes()).toContain('m-0'); expect(buttonLabelSubCopy.classes()).toContain('m-0');
expect(buttonLabelSubCopy.classes()).toContain('caption'); expect(buttonLabelSubCopy.classes()).toContain('button-label-sub-copy');
expect(buttonLabelSubCopy.classes()).toContain('ms-2');
expect(buttonLabelSubCopy.classes()).toContain(textPosition); expect(buttonLabelSubCopy.classes()).toContain(textPosition);
}); });
test('when "textPosition" prop not provided', async () => { test('when "textPosition" prop not provided', async () => {
@ -173,10 +172,9 @@ describe('Shop list button', () => {
// Assert // Assert
expect(buttonLabelSubCopy.exists()).toBeTruthy(); expect(buttonLabelSubCopy.exists()).toBeTruthy();
expect(buttonLabelSubCopy.classes().length).toBe(3); expect(buttonLabelSubCopy.classes().length).toBe(2);
expect(buttonLabelSubCopy.classes()).toContain('m-0'); expect(buttonLabelSubCopy.classes()).toContain('m-0');
expect(buttonLabelSubCopy.classes()).toContain('caption'); expect(buttonLabelSubCopy.classes()).toContain('button-label-sub-copy');
expect(buttonLabelSubCopy.classes()).toContain('ms-2');
}); });
}); });
describe('availability indicator block with expected when displayAvailabilityIndicators true', () => { describe('availability indicator block with expected when displayAvailabilityIndicators true', () => {
@ -378,8 +376,7 @@ describe('Shop list button', () => {
// Assert // Assert
expect(buttonBodyCopy.exists()).toBeTruthy(); expect(buttonBodyCopy.exists()).toBeTruthy();
expect(buttonBodyCopy.classes()).toContain('m-0'); expect(buttonBodyCopy.classes()).toContain('m-0');
expect(buttonBodyCopy.classes()).toContain('button-label-sub-copy'); expect(buttonBodyCopy.classes()).toContain('button-body-copy');
expect(buttonBodyCopy.classes()).toContain('small');
}); });
test('screen reader only text when screenReaderOnlyText provided', async () => { test('screen reader only text when screenReaderOnlyText provided', async () => {
// Arrange // Arrange

View file

@ -5,35 +5,39 @@
<baseInputButton <baseInputButton
v-bind="$props" v-bind="$props"
v-model="selectedValue" v-model="selectedValue"
buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 mb-2"> buttonWrapperClasses="list-group base-input-button list-button rounded-3 d-flex flex-column w-100 no-hover mb-2">
<div <div
:aria-label="buttonLabel" :aria-label="buttonLabel"
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4"> class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
<div class="row-one"> <div class="row-one">
<div class="availability-indicator-spacer"></div>
<span <span
id="buttonLabelSpan" id="buttonLabelSpan"
class="m-0 button-label-copy" class="m-0 button-label-copy"
:class="textPosition">{{ buttonLabel }} :class="textPosition">{{ buttonLabel }}
</span> </span>
<span class="separator"></span>
<span <span
id="buttonLabelSubCopySpan" id="buttonLabelSubCopySpan"
class="m-0 caption ms-2" class="m-0 button-label-sub-copy"
:class="textPosition">{{ buttonLabelSubCopy }} :class="textPosition">{{ buttonLabelSubCopy }}
</span> </span>
<div <div class="availability-indicator-container">
v-if="displayAvailabilityIndicators"
id="availabilityIndicatorBlock"
class="availability-indicator rounded-pill"
:class="availabilityRatingClass">
<div <div
id="availabilityIndicator" v-if="displayAvailabilityIndicators"
class="d-flex align-items-center"> id="availabilityIndicatorBlock"
class="availability-indicator rounded-pill"
:class="availabilityRatingClass">
<div <div
id="availabilityBadge" id="availabilityIndicator"
class="availability-badge" class="d-flex align-items-center">
:class="availabilityRating == 'high' ? 'green' : 'orange'"> <div
id="availabilityBadge"
class="availability-badge"
:class="availabilityRatingClass">
</div>
<span class="m-0 button-auxillary-copy">{{ badgeText }}</span>
</div> </div>
<span class="m-0 button-auxillary-copy">{{ badgeText }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -41,7 +45,7 @@
<span <span
v-if="buttonBodyCopy" v-if="buttonBodyCopy"
id="buttonBodyCopy" id="buttonBodyCopy"
class="m-0 button-label-sub-copy small" class="m-0 button-body-copy"
v-html="buttonBodyCopy"></span> v-html="buttonBodyCopy"></span>
</div> </div>
<span <span
@ -103,116 +107,128 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@/styles/ux-variables-svg-strings.scss"; @import "@/styles/ux-variables-svg-strings.scss";
$heritage-border-radius: 60px;
$heritage-border-color: #CACBCC;
$heritage-box-shadow: 0 1px 5px rgba($black, 0.2);
$heritage-checked-background-color: #e7f1f6;
$heritage-checked-border-color: #0070d1;
.list-button { .list-button {
outline: none; outline: none;
input[type="radio"], input[type="radio"],
input[type="checkbox"] { input[type="checkbox"] {
position: static; //override bootstrap position: static; //override bootstrap
&:checked + .list-button-content {
&:focus-visible + .list-button-content { background: $heritage-checked-background-color;
box-shadow: 0 0 0 2.5px $blue; border-color: $heritage-checked-border-color;
} box-shadow: 0 0 0 1px $blue;
&:focus + .list-button-content { .button-label-copy {
box-shadow: 0 0 0 2.5px $blue; font-weight: 500;
} color: $black;
&:checked + .list-button-content { }
color: $black; }
font-weight: 500; &:focus:checked + .list-button-content {
background: $blue-100; border-width: 2.5px;
box-shadow: 0 0 0 1px $blue; }
} }
&:checked:focus + .list-button-content {
box-shadow: 0 0 0 2.5px $blue;
}
&:checked + .list-button-content p,
&:checked + .list-button-content span {
font-weight: 500;
}
}
} }
.list-button-content { .list-button-content {
color: $gray-600; color: $darker-gray;
position: relative; font-weight: $font-weight-normal;
background: $white; position: relative;
transition: all 150ms linear; background: $white;
border-radius: $border-radius-lg; transition: all 150ms linear;
border: 1px solid $gray-500; border-radius: $heritage-border-radius;
width: 100%; border: 1px solid $heritage-border-color;
outline: none; box-shadow: $heritage-box-shadow;
width: 100%;
span { outline: none;
&.small {
font-size: 0.75rem;
color: $gray-550;
}
}
} }
.button-content { .button-content {
row-gap: 0.25rem; row-gap: 0.25rem;
.row-one { .row-one {
display: flex; display: flex;
align-items: center; align-items: center;
line-height: 1.5rem; justify-content: center;
line-height: 1.5rem;
.button-label-copy { .separator {
font-weight: 500; height: 1.25rem;
} margin: 0rem .3125rem;
border-left: 1px solid $heritage-border-color;
}
.availability-indicator { .availability-indicator-spacer {
background-repeat: no-repeat; width: 8rem;
display: flex; margin-right: auto;
align-items: center; }
margin-left: auto;
padding: 0.125rem 0.5rem;
.availability-badge { .availability-indicator-container {
display: inline-flex; display: flex;
width: 13px; align-items: center;
height: 12px; justify-content: flex-end;
background-position: center; margin-left: auto;
background-repeat: no-repeat; width: 8rem;
margin: 0 0.25rem 0 0; }
&.green { .availability-indicator {
background-image: url($svg-shop-list-button-green-availability); background-repeat: no-repeat;
} display: flex;
align-items: center;
padding: 0.125rem 0.5rem;
&.orange { .availability-badge {
background-image: url($svg-shop-list-button-orange-availability); display: inline-flex;
} width: 13px;
} height: 12px;
.button-auxillary-copy { background-position: center;
box-sizing: border-box; background-repeat: no-repeat;
justify-content: right; margin: 0 0.25rem 0 0;
line-height: 1.25rem;
font-weight: 500;
font-size: 0.75rem;
align-items: center;
}
&.green { &.green {
color: $green-700; background-image: url($svg-shop-list-button-green-availability);
background-color: $green-100; }
}
&.orange { &.orange {
color: $orange-600; background-image: url($svg-shop-list-button-orange-availability);
background-color: $orange-100; }
}
&.gray { &.gray {
color: $gray-600; background-image: url(~@/assets/img/heritage-loader-blue.gif);
background-color: $gray-100; background-size: contain;
padding-left: 0.125rem; }
padding-right: 0.125rem; }
} .button-auxillary-copy {
} box-sizing: border-box;
} justify-content: right;
line-height: 1.25rem;
font-weight: 500;
font-size: 0.75rem;
align-items: center;
}
.row-two { &.green {
text-align: left; color: $green-700;
} background-color: $green-100;
}
&.orange {
color: $orange-600;
background-color: $orange-100;
}
&.gray {
color: $gray-600;
background-color: $gray-100;
padding-left: 0.125rem;
padding-right: 0.125rem;
}
}
}
.row-two {
text-align: center;
}
} }
</style> </style>

View file

@ -54,7 +54,7 @@ import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue'; import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue'; import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import buttonMain from '@/ux-components/button-main/button-main.vue'; import buttonMain from '@/ux-components/button-main/button-main.vue';
import buttonVariants from '@/constants/button-variants'; import { buttonVariants } from '@/constants/component-variants';
// Supporting files // Supporting files
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js';
@ -135,7 +135,8 @@ export default {
* @summary Steps to perform when forward button clicked. * @summary Steps to perform when forward button clicked.
*/ */
async forwardButtonAction() { async forwardButtonAction() {
if (this.selectedAnswer == null) { // If user clicked foward without selecting an option or user click on "start a new claim" button.
if (this.selectedAnswer == null || this.selectedAnswer === 'NewClaim') {
this.navigateForward(); this.navigateForward();
return; return;
} }
@ -154,6 +155,7 @@ export default {
} }
}, },
navigateForward() { navigateForward() {
this.mainStore.updateDuplicateCheckVisited(true);
if (!this.mainStore.isPolicyLookupSuccessful) { if (!this.mainStore.isPolicyLookupSuccessful) {
this.$router.navigate( this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED, this.navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,

View file

@ -15,7 +15,7 @@
<siteSubHeader <siteSubHeader
ref="siteSubHeader" ref="siteSubHeader"
cmsWidgetName="SiteSubHeaderWidget" cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" /> class="site-sub-header-container" />
<buttonQuestion <buttonQuestion
v-if="educatorEndorsement" v-if="educatorEndorsement"
ref="schoolPropertyQuestion" ref="schoolPropertyQuestion"
@ -192,17 +192,147 @@ export default {
min-height: 1px; min-height: 1px;
padding-left: .9375rem; padding-left: .9375rem;
padding-right: .9375rem; padding-right: .9375rem;
.windshield-chip-count-question {
:deep(.col:first-of-type) {
.list-button-horizontal {
border-bottom-left-radius: $border-radius-list-button;
border-top-left-radius: $border-radius-list-button;
&:hover {
box-shadow: none;
}
.list-button-horizontal-content {
border-bottom-left-radius: $border-radius-list-button;
border-top-left-radius: $border-radius-list-button;
border-right-width: 0;
span {
font-size: 1rem;
font-weight: 400;
line-height: 1.5rem;
color: #525656;
}
}
&.selected {
.list-button-horizontal-content {
span {
color: #000;
font-weight: 600;
}
}
}
}
}
:deep(.col:last-of-type) {
.list-button-horizontal {
border-bottom-right-radius: $border-radius-list-button;
border-top-right-radius: $border-radius-list-button;
&:hover {
box-shadow: none;
}
.list-button-horizontal-content {
border-bottom-right-radius: $border-radius-list-button;
border-top-right-radius: $border-radius-list-button;
span {
font-size: 1rem;
font-weight: 400;
line-height: 1.5rem;
color: #525656;
}
}
&.selected {
.list-button-horizontal-content {
span {
font-weight: 600;
color: #000;
}
}
}
}
}
:deep(.list-button-horizontal) {
.list-button-horizontal-content {
padding: 0.625rem;
border: 1px solid #b3b4b5;
height: 2.875rem;
}
&.selected {
.list-button-horizontal-content {
border: 1px solid #0070d1;
box-shadow: none;
}
}
input[type="radio"] {
&:focus + .list-button-horizontal-content {
box-shadow: none;
border: 2.5px solid #0070d1;
outline: none;
}
}
&.has-error {
border: none;
.list-button-horizontal-content {
border: 1px solid #db0020;
}
input[type="radio"] {
&:focus + .list-button-horizontal-content {
border: 2.5px solid #db0020;
}
}
}
}
:deep(fieldset) {
box-shadow: 0 1px 5px rgba(0, 0, 0, .2);
border-radius: $border-radius-list-button;
}
}
:deep(.question-text) {
line-height: 1.5rem;
margin-bottom: .3125rem;
& > span {
text-align: left;
line-height: 1.5rem;
font-weight: 600;
}
}
} }
} }
:deep(.subheader-primary) { :deep(.subheader-primary) {
margin-bottom: map-get($spacers, 2); line-height: 1.5rem;
h5 {
line-height: 1.5rem;
span {
font-size: 1rem;
font-weight: 500;
line-height: 1.5rem;
}
}
} }
:deep(.subheader-secondary) { :deep(.subheader-secondary) {
line-height: 1.5rem;
margin-top: 0.625rem;
p { p {
font-size: $h6-font-size; font-size: 1rem;
line-height: map-get($spacers, 5); font-weight: 400;
line-height: 1.5rem;
} }
} }
@ -213,18 +343,6 @@ export default {
justify-content: left !important; justify-content: left !important;
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
margin-bottom: map-get($spacers, 5);
} }
:deep(.question-text) {
& > span {
text-align: left;
line-height: map-get($spacers, 5);
}
margin-bottom: map-get($spacers, 2);
}
:deep(div.question-text.d-flex) {
margin-top: map-get($spacers, 4);
}
</style> </style>

View file

@ -127,7 +127,7 @@ describe('navigation', () => {
expect(wrapper.vm.navigateForward).toHaveBeenCalled(); expect(wrapper.vm.navigateForward).toHaveBeenCalled();
}); });
test('if the coverage policy verified navigate forward to policy-vehicle page', async () => { test('if the coverage policy verified navigate forward to vehicle damage page', async () => {
// Arrange // Arrange
const mockRegistrationAddress = { const mockRegistrationAddress = {
streetAddress: '1234 Main St', streetAddress: '1234 Main St',
@ -135,14 +135,6 @@ describe('navigation', () => {
state: 'OH', state: 'OH',
zipCode: '43215' zipCode: '43215'
}; };
const mockvehicles = [
{
vin: 'TEST_VIN'
},
{
vin: 'TEST_VIN2'
}
];
const { wrapper } = setupMocks(); const { wrapper } = setupMocks();
@ -152,13 +144,12 @@ describe('navigation', () => {
customerQuestions: { customerQuestions: {
addressQuestions: mockRegistrationAddress addressQuestions: mockRegistrationAddress
}, },
isCoverageEnabled: true, isCoverageEnabled: true
vehiclesCount: 2,
vehiclesFound: mockvehicles
}); });
useMainStore().order.policy.policyNumber = 'p_0001'; useMainStore().order.policy.policyNumber = 'p_0001';
useMainStore().order.policy.dateOfLoss = '2022-01-28'; useMainStore().order.policy.dateOfLoss = '2022-01-28';
useMainStore().order.vehicle.policyVehicleId = 0;
// Act // Act
@ -167,10 +158,111 @@ describe('navigation', () => {
// Assert // Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith( expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED, navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
undefined, undefined
{}, );
{}, });
mockvehicles
test('if the coverage policy unverified navigate forward to vehicle damage page', async () => {
// Arrange
const mockRegistrationAddress = {
streetAddress: '1234 Main St',
city: 'Columbus',
state: 'OH',
zipCode: '43215'
};
const { wrapper } = setupMocks();
await wrapper.setData({
firstName: 'KK',
lastName: 'KK',
customerQuestions: {
addressQuestions: mockRegistrationAddress
},
isCoverageEnabled: true
});
useMainStore().order.policy.policyNumber = 'p_0001';
useMainStore().order.policy.dateOfLoss = '2022-01-28';
useMainStore().order.vehicle.policyVehicleId = null;
// Act
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
undefined
);
});
test('if the coverage policy verified navigate back to welcome page', async () => {
// Arrange
const mockRegistrationAddress = {
streetAddress: '1234 Main St',
city: 'Columbus',
state: 'OH',
zipCode: '43215'
};
const { wrapper } = setupMocks();
await wrapper.setData({
firstName: 'KK',
lastName: 'KK',
customerQuestions: {
addressQuestions: mockRegistrationAddress
},
isCoverageEnabled: true
});
useMainStore().order.policy.policyNumber = 'p_0001';
useMainStore().order.policy.dateOfLoss = '2022-01-28';
useMainStore().order.vehicle.policyVehicleId = 0;
// Act
await wrapper.vm.backButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_POLICY_VERIFIED,
undefined
);
});
test('if the coverage policy unverified navigate back to vehicle-selection page', async () => {
// Arrange
const mockRegistrationAddress = {
streetAddress: '1234 Main St',
city: 'Columbus',
state: 'OH',
zipCode: '43215'
};
const { wrapper } = setupMocks();
await wrapper.setData({
firstName: 'KK',
lastName: 'KK',
customerQuestions: {
addressQuestions: mockRegistrationAddress
},
isCoverageEnabled: true
});
useMainStore().order.policy.policyNumber = 'p_0001';
useMainStore().order.policy.dateOfLoss = '2022-01-28';
useMainStore().order.vehicle.policyVehicleId = null;
// Act
await wrapper.vm.backButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
navigationScenarios.CLICKED_BACK_POLICY_UNVERIFIED,
undefined
); );
}); });
}); });

View file

@ -78,7 +78,7 @@
cmsWidgetName="SiteFooterWidget" cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid" :isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction" @ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" /> @backClicked="backButtonAction" />
</div> </div>
</div> </div>
</div> </div>
@ -138,7 +138,6 @@ export default {
data() { data() {
return { return {
customerQuestions: this.getPolicyHolderDetailsFromStore(), customerQuestions: this.getPolicyHolderDetailsFromStore(),
vehiclesFound: [],
rules: { rules: {
firstName: globalRules.POLICYHOLDER_FIRST_NAME_REQUIRED, firstName: globalRules.POLICYHOLDER_FIRST_NAME_REQUIRED,
lastName: globalRules.POLICYHOLDER_LAST_NAME_REQUIRED, lastName: globalRules.POLICYHOLDER_LAST_NAME_REQUIRED,
@ -152,8 +151,8 @@ export default {
isCoverageEnabled() { isCoverageEnabled() {
return this.mainStore.issConfig.isCoverageEnabled; return this.mainStore.issConfig.isCoverageEnabled;
}, },
vehiclesCount() { isPolicyVehicleSelected() {
return this.vehiclesFound.length; return (this.mainStore.order.vehicle.policyVehicleId != null && this.mainStore.order.vehicle.policyVehicleId >= 0);
} }
}, },
methods: { methods: {
@ -163,13 +162,10 @@ export default {
}, },
navigateForward() { navigateForward() {
if (this.vehiclesCount > 0) { if (this.isPolicyVehicleSelected) {
this.$router.navigate( this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED, this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
this.$route, this.$route
{},
{},
this.vehiclesFound
); );
} else { } else {
this.$router.navigate( this.$router.navigate(
@ -179,6 +175,20 @@ export default {
} }
}, },
backButtonAction() {
if (this.isPolicyVehicleSelected) {
this.$router.navigate(
this.navigationScenarios.CLICKED_BACK_POLICY_VERIFIED,
this.$route
);
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_BACK_POLICY_UNVERIFIED,
this.$route
);
}
},
getPolicyHolderDetailsFromStore() { getPolicyHolderDetailsFromStore() {
return { return {
addressQuestions: { addressQuestions: {
@ -194,7 +204,7 @@ export default {
lastName: this.mainStore.order.customer.lastName, lastName: this.mainStore.order.customer.lastName,
phoneNumber: this.mainStore.order.contactInfo.homePhone, phoneNumber: this.mainStore.order.contactInfo.homePhone,
extension: this.mainStore.order.contactInfo.extension, extension: this.mainStore.order.contactInfo.extension,
email: this.mainStore.order.customer.emailAddress, email: this.mainStore.order.customer.emailAddress
}; };
} }
} }

View file

@ -70,7 +70,7 @@ import coverageType from '@/constants/coverage-type';
import alert from '@/ux-components/alert/alert.vue'; import alert from '@/ux-components/alert/alert.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue'; import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import buttonMain from '@/ux-components/button-main/button-main.vue'; import buttonMain from '@/ux-components/button-main/button-main.vue';
import buttonVariants from '@/constants/button-variants'; import { buttonVariants } from '@/constants/component-variants';
export default { export default {
name: 'policy-vehicles', name: 'policy-vehicles',

View file

@ -29,7 +29,7 @@
v-html="scheduleWithOtherText"></div> v-html="scheduleWithOtherText"></div>
<textLink <textLink
class="underlined-text" class="underlined-text"
linkType="navigation" linkType="text"
text="Find another shop" text="Find another shop"
href="#" href="#"
@clickEvent="findAnotherShopClicked" /> @clickEvent="findAnotherShopClicked" />

View file

@ -1,24 +1,21 @@
<template> <template>
<transition <transition
name="fade" name="fade"
mode="out-in"> mode="out-in">
<div <div
v-if="isDisplayed" class="appointment-type-question"
class="appointment-type-question" aria-live="polite">
aria-live="polite"> <buttonQuestion
<buttonQuestion ref="buttonQuestion"
ref="buttonQuestion" v-model="selectedValues"
v-model="selectedValues" customButtonQuestionId="appointmentTypeQuestion"
customButtonQuestionId="appointmentTypeQuestion" :answers="answersToDisplay"
:questionText="questionText" :groupName="groupName"
:answers="answersToDisplay" :suppressError="suppressError"
:groupName="groupName" :validationRules="validationRules"
buttonTypeString="listCard" isRequired />
:suppressError="suppressError" </div>
:validationRules="validationRules" </transition>
isRequired />
</div>
</transition>
</template> </template>
<script> <script>
@ -38,12 +35,9 @@ export default {
modelValue: String, modelValue: String,
groupName: String, groupName: String,
isAvailable: Boolean, isAvailable: Boolean,
isDisplayed: Boolean,
suppressError: Boolean, suppressError: Boolean,
validationRules: String, validationRules: String,
cmsWidgetName: String, cmsWidgetName: String
isServiceableMobile: Boolean,
isServiceableInshop: Boolean
}, },
emits: ['update:modelValue'], emits: ['update:modelValue'],
computed: { computed: {
@ -51,23 +45,13 @@ export default {
return this.getCmsContent(this.cmsWidgetName, 'QuestionText'); return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
}, },
answersFromCms() { answersFromCms() {
const retCms = this.getCmsContent(this.cmsWidgetName, 'Answers'); return this.getCmsContent(this.cmsWidgetName, 'Answers');
if (retCms && retCms.length > 0 && this.isMobileFeeHidden) {
return retCms.map((x) => (x.Name === AppointmentTypeStrings.MOBILE ? { ...x, SubText: x.SubText.replace('*', '') } : x));
}
return retCms;
}, },
answersToDisplay() { answersToDisplay() {
const shouldShowMobile = this.isServiceableMobile && this.isItacOrNoComp;
const shouldShowMobileNotITACNotNoComp = this.isServiceableMobile && !this.isItacOrNoComp;
const shouldShowInshop = this.isServiceableInshop;
const shouldShowDropoff = this.isServiceableInshop && !useMainStore().damage.isRepair;
return this.answersFromCms return this.answersFromCms
? this.answersFromCms.filter((answer) => ( ? this.answersFromCms.filter((answer) => (
(answer.Name === AppointmentTypeStrings.IN_SHOP && shouldShowInshop) (answer.Name === AppointmentTypeStrings.IN_SHOP)
|| (answer.Name === AppointmentTypeStrings.MOBILE && shouldShowMobile) || (answer.Name === AppointmentTypeStrings.MOBILE)
|| (answer.Name === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP && shouldShowMobileNotITACNotNoComp)
|| (answer.Name === AppointmentTypeStrings.DROP_OFF && shouldShowDropoff)
)) ))
: []; : [];
}, },
@ -78,58 +62,13 @@ export default {
set(newValue) { set(newValue) {
this.$emit('update:modelValue', newValue); this.$emit('update:modelValue', newValue);
} }
},
isITAC() {
return useMainStore().isITAC;
},
isItacOrNoComp() {
return this.isITAC || useMainStore().isNoComp;
},
isMobileFeeHidden() {
return this.getSettingValue(experimentSettings.ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_HIDDEN) === 'true';
},
isMobileOnly() {
return this.isServiceableMobile && !this.isServiceableInshop;
} }
}, },
watch: {
answersToDisplay: {
handler(newValue) {
// If there is only one option to display and that option is 'Mobile' then select it
if (
newValue.length === 1
&& newValue.findIndex((answer) => (answer.Name === AppointmentTypeStrings.MOBILE
|| answer.Name === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)) !== -1
) {
if (this.isItacOrNoComp) {
this.selectedValues = AppointmentTypeStrings.MOBILE;
} else {
this.selectedValues = AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
}
}
},
immediate: true
},
isMobileOnly: {
handler(newValue) {
if (newValue) {
if (this.isItacOrNoComp) {
this.selectedValues = AppointmentTypeStrings.MOBILE;
} else {
this.selectedValues = AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
}
}
}
}
}
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.button-question) { .appointment-type-question {
.list-card img { margin-bottom: 1.25rem;
height: auto;
width: 3.417rem;
}
} }
</style> </style>

View file

@ -1,29 +1,13 @@
/* eslint-env jest */
import baseMixin from '@/mixins/base-mixin'; import baseMixin from '@/mixins/base-mixin';
import { shallowMount } from '@vue/test-utils'; import { mount, flushPromises } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { createTestingPinia } from '@pinia/testing';
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
import navigationScenarios from '@/router/router-constants/navigation-scenarios';
import routerParams from '@/router/router-constants/router-params';
import { useMainStore } from '@/store';
import serviceLocation from '@/layouts/service-location/service-location.vue'; import serviceLocation from '@/layouts/service-location/service-location.vue';
import { getZipCodeData } from '@/helpers/service-location-helper';
// Define Mocks
jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn(() => Promise.resolve('content'))
}));
/** @ignore */
function setupMocks() {
const wrapper = shallowMount(
serviceLocation,
getMountOptions({
router: {
navigate: jest.fn()
}
})
);
wrapper.vm.$router.navigateWithSpinner = jest.fn();
wrapper.vm.navigateBack = baseMixin.methods.navigateBack;
return { wrapper };
}
const mockGetServiceabilityDetails = () => { const mockGetServiceabilityDetails = () => {
const serviceabilityDetails = { const serviceabilityDetails = {
@ -35,123 +19,199 @@ const mockGetServiceabilityDetails = () => {
return Promise.resolve(serviceabilityDetails); return Promise.resolve(serviceabilityDetails);
}; };
const mockZipcodeData = (zip) => {
if (zip === '43235' || zip === '55555') {
return Promise.resolve({
containsMilitaryBase: false,
isValid: true,
isServiceable: true,
city: 'Columbus',
state: 'OH',
zipCodeCtu: '01820'
});
}
if (zip === '45433') {
return Promise.resolve({
containsMilitaryBase: true,
isValid: true,
isServiceable: true,
city: 'Columbus',
state: 'OH',
zipCodeCtu: '01820'
});
}
return Promise.resolve({
containsMilitaryBase: false,
isValid: false,
isServiceable: false,
city: null,
state: null,
zipCodeCtu: null
});
}
const mockProviders = () => {
return Promise.resolve([
{
"address": {
"city": "COLUMBUS",
"country": "US",
"state": "OH",
"streetAddress": "6826 Sawmill Rd",
"streetAddress2": "",
"zipCode": "43235",
"zipCodeCtu": "03357"
},
"distanceInMiles": 4.136335989015438,
"providerNumber": "003357",
"companyName": "SAFELITE AUTOGLASS - COLUMBUS, OH",
"phoneNumber": "6142336400",
"isSafeliteShop": true
},
{
"address": {
"city": "Lewis Center",
"country": "US",
"state": "OH",
"streetAddress": "1343 Cameron Ave",
"streetAddress2": "",
"zipCode": "43035",
"zipCodeCtu": "03357"
},
"distanceInMiles": 8.193072412262042,
"providerNumber": "003417",
"companyName": "SAFELITE AUTOGLASS - LEWIS CENTER, OH",
"phoneNumber": "6147815433",
"isSafeliteShop": true
}
])
}
jest.mock( jest.mock(
'@/helpers/service-location-helper', '@/helpers/service-location-helper',
() => ({ () => ({
getServiceabilityDetails: jest.fn((mockServiceZipCode) => mockGetServiceabilityDetails(mockServiceZipCode)) getServiceabilityDetails: jest.fn((mockServiceZipCode) => mockGetServiceabilityDetails(mockServiceZipCode)),
getZipCodeData: jest.fn((mockServiceZipCode) => mockZipcodeData(mockServiceZipCode)),
getMobileZipCodeData: jest.fn((mockServiceZipCode) => mockZipcodeData(mockServiceZipCode))
}) })
); );
const mockMixin = { const mockRoute = {
methods: { params: {}
getCmsContent: jest.fn((widgetName, cmsFieldName) => { };
// linkWidgetName is not defined. This whole spec file needs review. const mockRouter = {
if (widgetName === linkWidgetName) { navigate: jest.fn()
return mockLinkCmsContent[cmsFieldName]; };
const mountOptions = {
global: {
mixins: [
{
computed: {
navigationScenarios() {
return navigationScenarios;
},
routerParams() {
return routerParams;
}
},
methods: {
getCmsContent: jest.fn(),
getFooterInfoBoxHeight: jest.fn(() => 80),
getPageNameByQueryString: jest.fn(() => '')
}
} }
],
if (widgetName === modalWidgetName) { mocks: {
return mockModalCmsContent[cmsFieldName]; $route: mockRoute,
} $router: mockRouter
},
return null; stubs: {
}), alert: true,
textBlock: true,
getZipCodeData: jest.fn((zip) => { appointmentTypeQuestion: true,
if (zip === '43235' || zip === '55555') { contentGroupModal: true,
return Promise.resolve({ siteFooter: true,
containsMilitaryBase: false, siteHeader: true,
isValid: true, siteSubHeader: true,
state: 'OH', shopAddress: true,
zipCodeCtu: '01820' serviceZipQuestion: true
}); }
}
if (zip === '45433') {
return Promise.resolve({
containsMilitaryBase: true,
isValid: true,
state: 'OH'
});
}
return Promise.resolve({
containsMilitaryBase: false,
isValid: false,
state: null,
zipCodeCtu: null
});
}),
onSubmit: jest.fn(),
onInvalidSubmit: jest.fn()
} }
}; };
function setupMocks({ appointmentType = AppointmentTypeStrings.IN_SHOP }) {
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: {
order: {
serviceLocation: {
appointmentType: appointmentType
}
}
}
}
})];
useMainStore().getProviders = jest.fn().mockImplementation(() => mockProviders());
const wrapper = mount(serviceLocation, mountOptions);
describe('navigation', () => { wrapper.vm.$router.navigateWithSpinner = jest.fn();
test('if the back button is clicked, navigate back', async () => { wrapper.vm.navigateBack = baseMixin.methods.navigateBack;
// Arrange
const { wrapper } = setupMocks({ return { wrapper };
}
beforeEach(() => {
jest.clearAllMocks();
});
describe('service-location.vue', () => {
describe('navigation', () => {
test('if the back button is clicked, navigate back', async () => {
// Arrange
const { wrapper } = setupMocks({
});
// Act
await wrapper.vm.navigateBack();
// Assert
expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalled();
});
});
describe('updating service zip', () => {
test('updates the page model after changing the service zip code', () => {
// Arrange
const { wrapper } = setupMocks({ appointmentType: AppointmentTypeStrings.IN_SHOP });
const newServiceZipCode = '61606';
const shopAddressComponent = wrapper.findComponent({
ref: 'shopAddress'
});
// Act
shopAddressComponent.vm.$emit('zip-updated', newServiceZipCode);
// Assert
expect(wrapper.vm.zipCode).toStrictEqual(newServiceZipCode);
}); });
// Act test('displays military zip message when zip is updated', async () => {
await wrapper.vm.navigateBack(); // Arrange
const { wrapper } = setupMocks({ appointmentType: AppointmentTypeStrings.MOBILE });
// Assert const mobileServiceZipCodeQuestion = wrapper.findComponent({
expect(wrapper.vm.$router.navigateWithSpinner).toHaveBeenCalled(); ref: 'mobileServiceZipCodeQuestion'
}); });
});
expect(wrapper.vm.zipContainsMilitaryBase).toBe(false);
describe('updating service zip', () => {
test('updates the page model after changing the service zip code', () => { const newMobileServiceZipCode = '45433'
// Arrange
const { wrapper } = setupMocks({ // Act
mixins: [mockMixin] mobileServiceZipCodeQuestion.vm.$emit('update:modelValue', newMobileServiceZipCode);
}); await wrapper.vm.$nextTick();
const newServiceZipCodeQuestion = { // Assert
zipCode: '61606', expect(wrapper.vm.zipContainsMilitaryBase).toBe(true);
state: 'IL' });
};
const serviceZipCodeComponent = wrapper.findComponent({
ref: 'serviceZipCodeQuestion'
});
// Act
serviceZipCodeComponent.vm.$emit('update:modelValue', newServiceZipCodeQuestion);
// Assert
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeQuestion);
});
test('displays military zip message when zip is updated', () => {
// Arrange
const { wrapper } = setupMocks({});
const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: 'mobileLocationQuestions'
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const serviceZipCodeComponent = wrapper.findComponent({
ref: 'serviceZipCodeQuestion'
});
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
expect(wrapper.vm.zipContainsMilitaryBase).toBe(false);
// TODO: Use or remove
const newServiceZipCodeQuestion = {
zipCode: '45433',
state: 'OH'
};
// Act
serviceZipCodeComponent.vm.$emit('updated-contains-military-base', true);
// Assert
expect(wrapper.vm.zipContainsMilitaryBase).toBe(true);
}); });
}); });

View file

@ -12,28 +12,7 @@
<div class="service-location-container iss-heritage-content-container-width"> <div class="service-location-container iss-heritage-content-container-width">
<siteSubHeader <siteSubHeader
cmsWidgetName="SiteSubHeaderWidget" cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" /> class="subheader" />
<serviceZipModalQuestion
ref="serviceZipCodeQuestion"
v-model="serviceZipCodeQuestion"
modalWidgetName="ServiceZipModalWidget"
:onZipUpdateCallback="reloadShopData"
@updatedServiceability="setServiceabilityDetails"
@updatedContainsMilitaryBase="
setContainsMilitaryBase
" />
<alert
v-if="displayMilitaryZipAlert"
ref="alertMilitaryBaseZip"
class="my-5"
cmsWidgetName="AlertMilitaryBaseZipWidget"
alertClass="alert-warning" />
<alert
v-if="displayServiceableMobileOnly"
ref="alertMobileOnly"
class="my-5"
cmsWidgetName="AlertMobileOnlyWidget"
alertClass="alert-warning" />
<alert <alert
v-if="displayRecalibrationWarning" v-if="displayRecalibrationWarning"
ref="alertRecalNoMobile" ref="alertRecalNoMobile"
@ -41,18 +20,6 @@
cmsWidgetName="AlertRecalNoMobileWidget" cmsWidgetName="AlertRecalNoMobileWidget"
alertClass="alert-warning" alertClass="alert-warning"
@text-link-clicked="openModalAction" /> @text-link-clicked="openModalAction" />
<alert
v-if="displayServiceableInshopOnly"
ref="alertInshopOnly"
class="my-5"
cmsWidgetName="AlertInshopOnlyWidget"
alertClass="alert-warning" />
<alert
v-if="displayNoShopsAlert"
ref="alertNoShops"
class="my-5"
cmsWidgetName="AlertNoShopsWidget"
alertClass="alert-warning" />
<alert <alert
v-if="displayBigTruckNoShops" v-if="displayBigTruckNoShops"
ref="alertBigTruckNoShops" ref="alertBigTruckNoShops"
@ -60,41 +27,94 @@
cmsWidgetName="AlertBigTruckNoShopsWidget" cmsWidgetName="AlertBigTruckNoShopsWidget"
alertClass="alert-warning" /> alertClass="alert-warning" />
<div class="appointment-type"> <div class="appointment-type">
<div class="appointment-type-question-text d-flex">
<span class="w-100 recal-text" v-if="recalibrationRequired">{{ scheduleRecalText }}</span>
<span class="w-100" v-if="!requiresInshopRecalibration">{{ appointmentQuestionText }}</span>
</div>
<alert
v-if="displayServiceableInshopOnly"
ref="alertInshopOnly"
cmsWidgetName="AlertInshopOnlyWidget"
:manualCopy="inShopOnlyCopy"
:isCollapsible="true"
alertClass="alert-warning" />
<alert
v-if="displayServiceableMobileOnly"
ref="alertMobileOnly"
cmsWidgetName="AlertMobileOnlyWidget"
:manualCopy="mobileOnlyCopy"
:isCollapsible="true"
alertClass="alert-warning" />
<appointmentTypeQuestion <appointmentTypeQuestion
v-show="isAppointmentTypeDisplayed" v-if="!requiresInshopRecalibration && isServiceableMobile"
ref="appointmentTypeQuestion" ref="appointmentTypeQuestion"
v-model="selectedAppointmentType" v-model="selectedAppointmentType"
:isServiceableMobile="isServiceableMobile"
:isServiceableInshop="isServiceableInshop"
:isDisplayed="isAppointmentTypeDisplayed"
groupName="appointmentTypeQuestion" groupName="appointmentTypeQuestion"
cmsWidgetName="AppointmentTypeQuestionWidget" cmsWidgetName="AppointmentTypeQuestionWidget"
validationRules="option-required" /> validationRules="option-required" />
</div> </div>
<mobileLocationModalQuestions <div
v-if="isMobileLocationDisplayed" v-if="isInshop">
ref="mobileLocationQuestions" <alert
v-model="mobileLocationQuestions" v-if="displayLowAvailabilityInshop"
customComponentId="mobileLocationQuestions" ref="alertLowAvailabilityInshop"
:mobileFeePart="mobileFeePart" cmsWidgetName="AlertLowAvailabilityInshopWidget"
validationRules="mobile-location-required" :isCollapsible="true"
linkWidgetName="MobileLocationLinkWidget" alertClass="alert-warning" />
modalWidgetName="MobileLocationModalWidget" <shopAddress
:onZipUpdateCallback="reloadShopData" ref="shopAddress"
@updated-mobile-fee-part="setMobileFeePart" v-model="selectedProvider"
@updated-serviceability="setServiceabilityDetails" :serviceZipcode="zipCode"
@updated-contains-military-base="setContainsMilitaryBase" :selectedAppointmentType="selectedAppointmentType"
@updated-mobile-ctu="setCtuForMobile" /> modalWidgetName="ChangeLocationModalWidget"
<shopQuestion cmsWidgetName="ShopAddressWidget"
v-show="isShopQuestionDisplayed" @zip-updated="handleInShopZipUpdated" />
:ref="SHOP_QUESTION_REF_NAME" <!-- INTEGRATION TODO: Put this right under the calendar -->
v-model="selectedProvider" <alert
:selectedAppointmentType="selectedAppointmentType" v-if="selectedProvider === null"
:isDisplayed="isShopQuestionDisplayed" ref="alertNoAvailableShops"
cmsWidgetName="ShopQuestionWidget" cmsWidgetName="AlertNoAvailableShopsWidget"
@updatedMobileProviderNumber=" :isCollapsible="true"
setMobileProviderNumber alertClass="alert-danger" />
" /> </div>
<div
v-if="isMobile">
<div class="expandable-link-container">
<a
v-if="displayMilitaryZipAlert && !militaryBaseWarningExpanded"
href="#"
class="expandable-link"
@click.prevent="militaryBaseWarningExpanded = true">
{{ militaryBaseWarningLinkText }}
</a>
<div
v-if="displayMilitaryZipAlert && militaryBaseWarningExpanded"
class="expandable-link-text">
{{ militaryBaseWarningText }}
</div>
</div>
<serviceZipQuestion
ref="mobileServiceZipCodeQuestion"
v-model="mobileZipCode"
customInputId="mobileServiceZipCode"
class="mobile-service-zip-question"
:placeholderText="mobileZipPlaceholder"
:serviceZipFormatErrorMessage="errorMessages.MOBILE_SERVICE_ZIP_FORMAT"
:hasError="mobileZipError !== ''"
cmsWidgetName="MobileZipWidget" />
<div
v-if="mobileZipError"
ref="errorMessageDiv"
class="row my-1 form-test-error">
<span
class="d-inline-flex mt-0"
role="alert">{{ mobileZipError }}</span>
</div>
<textBlock
v-if="displayMobileFeeDisclaimer"
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="disclaimer" />
</div>
<contentGroupModal <contentGroupModal
:ref="RECAL_MODAL_REF_NAME" :ref="RECAL_MODAL_REF_NAME"
cssModalHeadlineClass="text-center" cssModalHeadlineClass="text-center"
@ -104,7 +124,7 @@
class="mt-5" class="mt-5"
cmsWidgetName="SiteFooterWidget" cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled=" :isForwardActionDisabled="
!meta.valid || displayNoShopsAlert !meta.valid || displayNoShopsAlert || displayBigTruckNoShops
" "
@backClicked="navigateBack(this, navigateBackScenario)" @backClicked="navigateBack(this, navigateBackScenario)"
@forwardClicked="forwardButtonAction" /> @forwardClicked="forwardButtonAction" />
@ -118,44 +138,31 @@
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js'; import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper'; import settleAllPromises from '@/helpers/layout-helper';
import { required } from '@/helpers/validation-rules';
import errorMessages from '@/constants/error-messages'; import errorMessages from '@/constants/error-messages';
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import { import {
getPricedMobileFeePart, getPricedMobileFeePart,
getServiceabilityDetails, getServiceabilityDetails,
getZipCodeData getZipCodeData,
getMobileZipCodeData
} from '@/helpers/service-location-helper'; } from '@/helpers/service-location-helper';
import { toTitleCase } from '@/helpers/text-helper.js';
import { getAvailabilityRating } from '@/helpers/service-location-helper';
// Import Component // Import Component
import alert from '@/ux-components/alert/alert.vue'; import alert from '@/ux-components/alert/alert.vue';
import textBlock from '@/digital-components/text-block/text-block.vue';
import appointmentTypeQuestion from '@/layouts/service-location/appointment-type-question/appointment-type-question.vue'; import appointmentTypeQuestion from '@/layouts/service-location/appointment-type-question/appointment-type-question.vue';
import baseFormMixin from '@/mixins/base-form-mixin'; import baseFormMixin from '@/mixins/base-form-mixin';
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue'; import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
import mobileLocationModalQuestions from '@/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue';
import { Form, defineRule } from 'vee-validate'; import { Form, defineRule } from 'vee-validate';
import shopQuestion from '@/layouts/service-location/shop-question/shop-question.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue'; import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import siteHeader from '@/iss-components/site-header/site-header.vue'; import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue'; import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import serviceZipModalQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-modal-question.vue'; import shopAddress from '@/layouts/service-location/shop-address/shop-address.vue';
import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
import widgetFields from '@/constants/cms-widget-fields';
// DEFINE VALIDATION RULES
defineRule('mobile-location-required', (value) => {
if (
value.addressQuestions.streetAddress === ''
|| value.addressQuestions.city === ''
|| value.addressQuestions.state === ''
|| value.addressQuestions.zipCode === ''
|| value.isVehicleProtected == null
) {
return errorMessages.MOBILE_LOCATION_REQUIRED;
}
return true;
});
defineRule('selection-required', required(errorMessages.OPTION_REQUIRED));
const SHOP_QUESTION_REF_NAME = 'shopQuestion';
const RECAL_MODAL_REF_NAME = 'RecalModal'; const RECAL_MODAL_REF_NAME = 'RecalModal';
const SITE_FOOTER_REF_NAME = 'siteFooter'; const SITE_FOOTER_REF_NAME = 'siteFooter';
@ -163,16 +170,16 @@ export default {
name: 'service-location', name: 'service-location',
components: { components: {
alert, alert,
textBlock,
appointmentTypeQuestion, appointmentTypeQuestion,
contentGroupModal, contentGroupModal,
mobileLocationModalQuestions,
siteFooter, siteFooter,
siteHeader, siteHeader,
siteSubHeader, siteSubHeader,
// eslint-disable-next-line vue/no-reserved-component-names // eslint-disable-next-line vue/no-reserved-component-names
Form, Form,
serviceZipModalQuestion, shopAddress,
shopQuestion serviceZipQuestion
}, },
mixins: [baseFormMixin], mixins: [baseFormMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -182,8 +189,8 @@ export default {
const zipCodeData = getZipCodeData(serviceZipCode); const zipCodeData = getZipCodeData(serviceZipCode);
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode); const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode); const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode);
const getGlassFeesPromise = useMainStore().getGlassFees(); const getGlassFeesPromise = useMainStore().getGlassFees();
const providersPromise = useMainStore().getProviders(serviceZipCode);
// Settle promises and get results // Settle promises and get results
const promiseResultMap = [ const promiseResultMap = [
@ -203,13 +210,13 @@ export default {
resultKey: 'serviceabilityDetails', resultKey: 'serviceabilityDetails',
promise: serviceabilityDetailsPromise promise: serviceabilityDetailsPromise
}, },
{
resultKey: 'shopQuestionInitialData',
promise: shopQuestionInitialDataPromise
},
{ {
resultKey: 'zipCodeData', resultKey: 'zipCodeData',
promise: zipCodeData promise: zipCodeData
},
{
resultKey: 'providers',
promise: providersPromise
} }
]; ];
@ -221,10 +228,9 @@ export default {
resultMap.zipCodeData, resultMap.zipCodeData,
resultMap.serviceabilityDetails, resultMap.serviceabilityDetails,
resultMap.mobileFeePart, resultMap.mobileFeePart,
resultMap.shopQuestionInitialData?.mobileProviderNumber, resultMap.glassFees,
resultMap.glassFees resultMap.providers
); );
vm.$refs[SHOP_QUESTION_REF_NAME].initializeComponent(resultMap.shopQuestionInitialData);
}); });
}, },
setup() { setup() {
@ -238,7 +244,6 @@ export default {
city: this.getServiceCityFromStore(), city: this.getServiceCityFromStore(),
state: this.getServiceStateFromStore(), state: this.getServiceStateFromStore(),
zipCode: this.getServiceZipCodeFromStore(), zipCode: this.getServiceZipCodeFromStore(),
isVehicleProtected: this.getIsVehicleProtectedFromStore(),
isGlassServiceableInshop: null, isGlassServiceableInshop: null,
isRecalibrationServiceableInshop: null, isRecalibrationServiceableInshop: null,
isGlassServiceableMobile: null, isGlassServiceableMobile: null,
@ -249,9 +254,13 @@ export default {
mobileProviderNumber: null, mobileProviderNumber: null,
zipContainsMilitaryBase: false, zipContainsMilitaryBase: false,
zipCodeCtu: null, zipCodeCtu: null,
SHOP_QUESTION_REF_NAME, mobileZipCode: '',
mobileZipError: '',
militaryBaseWarningExpanded: false,
availabilityRating: null,
RECAL_MODAL_REF_NAME, RECAL_MODAL_REF_NAME,
SITE_FOOTER_REF_NAME SITE_FOOTER_REF_NAME,
errorMessages
}; };
}, },
computed: { computed: {
@ -264,61 +273,6 @@ export default {
answersFromCms() { answersFromCms() {
return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers'); return this.getCmsContent('ServiceTypeQuestionWidget', 'Answers');
}, },
serviceZipCodeQuestion: {
get() {
return {
state: this.state,
zipCode: this.zipCode
};
},
set(newValue) {
if (newValue.zipCode !== this.zipCode) {
this.resetMobileLocation();
this.selectedAppointmentType = null;
this.selectedProvider = null;
}
this.state = newValue.state;
this.zipCode = newValue.zipCode;
// eslint-disable-next-line vue/valid-next-tick
this.$nextTick();
}
},
mobileLocationQuestions: {
get() {
return {
addressQuestions: {
streetAddress: this.streetAddress,
streetAddress2: this.streetAddress2,
city: this.city,
state: this.state,
zipCode: this.zipCode
},
isVehicleProtected: this.isVehicleProtected
};
},
set(newValue) {
this.streetAddress = newValue.addressQuestions.streetAddress;
this.streetAddress2 = newValue.addressQuestions.streetAddress2;
this.city = newValue.addressQuestions.city;
this.state = newValue.addressQuestions.state;
this.zipCode = newValue.addressQuestions.zipCode;
this.isVehicleProtected = newValue.isVehicleProtected;
if (newValue.zipCode !== this.zipCode) {
if (
!(
this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
)
) {
this.selectedAppointmentType = null;
}
this.selectedProvider = null;
}
}
},
isServiceableMobile() { isServiceableMobile() {
if (this.isRecalibrationServiceableMobile !== null) { if (this.isRecalibrationServiceableMobile !== null) {
return ( return (
@ -338,22 +292,14 @@ export default {
return this.isGlassServiceableInshop; return this.isGlassServiceableInshop;
}, },
isShopQuestionDisplayed() { isInshop() {
return ( return (
this.selectedAppointmentType === 'Inshop' this.selectedAppointmentType === 'Inshop'
|| this.selectedAppointmentType === 'Dropoff' || this.selectedAppointmentType === 'Dropoff'
); );
}, },
isAppointmentTypeDisplayed() { isMobile() {
return this.zipCode && !this.displayNoShopsAlert; return this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
},
isMobileLocationDisplayed() {
return (
this.selectedAppointmentType
=== AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType
=== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
);
}, },
requiresInshopRecalibration() { requiresInshopRecalibration() {
// Specifically check for isRecalibrationServiceableMobile === false, not null or true. // Specifically check for isRecalibrationServiceableMobile === false, not null or true.
@ -375,15 +321,17 @@ export default {
displayRecalibrationWarning() { displayRecalibrationWarning() {
return this.requiresInshopRecalibration; return this.requiresInshopRecalibration;
}, },
displayLowAvailabilityInshop() {
return this.availabilityRating === 'low' && this.isInshop;
},
displayServiceableInshopOnly() { displayServiceableInshopOnly() {
return ( return !this.isServiceableMobile && this.isServiceableInshop && !this.displayRecalibrationWarning;
!this.displayRecalibrationWarning
&& this.isServiceableInshop
&& !this.isServiceableMobile
);
}, },
displayServiceableMobileOnly() { displayServiceableMobileOnly() {
return this.isServiceableMobile && !this.isServiceableInshop; return this.isServiceableMobile && this.recalibrationRequired && !this.isServiceableInshop;
},
displayMobileFeeDisclaimer() {
return this.mainStore.isNoComp || this.mainStore.isITAC;
}, },
navigateBackScenario() { navigateBackScenario() {
const { isNoComp, isITAC } = useMainStore(); const { isNoComp, isITAC } = useMainStore();
@ -393,6 +341,38 @@ export default {
}, },
isBigTruck() { isBigTruck() {
return this.mainStore.order.vehicle.isBigTruck; return this.mainStore.order.vehicle.isBigTruck;
},
mobileZipPlaceholder() {
return this.getCmsContent('MobileZipPlaceHolderWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
militaryBaseWarningLinkText() {
return this.getCmsContent('AlertMilitaryBaseZipWidget', widgetFields.ALERT_WIDGET.HEADLINE_TEXT);
},
militaryBaseWarningText() {
return this.getCmsContent('AlertMilitaryBaseZipWidget', widgetFields.ALERT_WIDGET.BODY_TEXT);
},
appointmentQuestionText() {
return this.getCmsContent('WhereWouldYouLikeServiceWidget', widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT);
},
scheduleRecalText() {
if(this.requiresInshopRecalibration) {
return this.getCmsContent('WhereWouldYouLikeServiceWidget', widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2);
} else {
return this.getCmsContent('WhereWouldYouLikeServiceWidget', widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT);
}
},
inShopOnlyCopy() {
let content = this.getCmsContent('AlertInshopOnlyWidget', widgetFields.ALERT_WIDGET.BODY_TEXT);
content = content.replace('{custom:city}', toTitleCase(this.city));
return content;
},
mobileOnlyCopy() {
let content = this.getCmsContent('AlertMobileOnlyWidget', widgetFields.ALERT_WIDGET.BODY_TEXT);
content = content.replace('{custom:city}', toTitleCase(this.city));
return content;
},
recalibrationRequired() {
return this.mainStore.hasRecalibrationPart;
} }
}, },
methods: { methods: {
@ -401,13 +381,10 @@ export default {
useMainStore().order.serviceLocation.zipCode !== null useMainStore().order.serviceLocation.zipCode !== null
); );
}, },
async reloadShopData(zipCode) {
await this.$refs[SHOP_QUESTION_REF_NAME].reloadShopData(zipCode);
},
async forwardButtonAction() { async forwardButtonAction() {
let provider = this.selectedProvider; let provider = this.selectedProvider;
this.mainStore.updateMobileFee(null); this.mainStore.updateMobileFee(null);
if (this.isMobileLocationDisplayed) { if (this.isMobile) {
if (this.mainStore.isNoComp || this.mainStore.isITAC) { if (this.mainStore.isNoComp || this.mainStore.isITAC) {
this.mainStore.updateMobileFee(this.mobileFeePart); this.mainStore.updateMobileFee(this.mobileFeePart);
} }
@ -432,7 +409,6 @@ export default {
zipCode: this.zipCode, zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu, zipCodeCtu: this.zipCodeCtu,
appointmentType: this.selectedAppointmentType, appointmentType: this.selectedAppointmentType,
isVehicleProtected: this.isVehicleProtected,
provider provider
}); });
@ -466,9 +442,6 @@ export default {
|| useMainStore().order.customer.address.zipCode || useMainStore().order.customer.address.zipCode
); );
}, },
getIsVehicleProtectedFromStore() {
return useMainStore().order.serviceLocation.isVehicleProtected;
},
getSelectedAppointmentType() { getSelectedAppointmentType() {
return useMainStore().order.serviceLocation.appointmentType; return useMainStore().order.serviceLocation.appointmentType;
}, },
@ -482,12 +455,13 @@ export default {
zipCodeData, zipCodeData,
serviceabilityDetails, serviceabilityDetails,
mobileFeePart, mobileFeePart,
mobileProviderNumber, glassFees,
glassFees providers
) { ) {
if (zipCodeData) { if (zipCodeData) {
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase; this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
this.zipCodeCtu = zipCodeData.zipCodeCtu; this.zipCodeCtu = zipCodeData.zipCodeCtu;
this.city = zipCodeData.city;
} }
if (serviceabilityDetails) { if (serviceabilityDetails) {
@ -498,8 +472,24 @@ export default {
this.mobileFeePart = mobileFeePart; this.mobileFeePart = mobileFeePart;
} }
if (mobileProviderNumber) { if (providers) {
this.setMobileProviderNumber(mobileProviderNumber); this.setMobileProviderNumber(providers.mobileProviderNumber);
let foundMatch = false;
if(this.selectedProvider && this.selectedProvider.providerNumber) {
const matchedProvider = providers.shopProviders.find(
(provider) => provider.providerNumber === this.selectedProvider.providerNumber
);
if (matchedProvider) {
foundMatch = true;
this.selectedProvider = matchedProvider;
}
}
if(providers.shopProviders.length > 0 && !foundMatch) {
this.selectedProvider = providers.shopProviders[0];
}
else if(providers.shopProviders.length === 0) {
this.selectedProvider = null;
}
} }
if (glassFees) { if (glassFees) {
@ -521,13 +511,6 @@ export default {
setMobileProviderNumber(providerNumber) { setMobileProviderNumber(providerNumber) {
this.mobileProviderNumber = providerNumber; this.mobileProviderNumber = providerNumber;
}, },
resetMobileLocation() {
this.streetAddress = '';
this.streetAddress2 = '';
this.city = '';
this.isVehicleProtected = null;
},
setServiceabilityDetails(serviceabilityDetails) { setServiceabilityDetails(serviceabilityDetails) {
this.isGlassServiceableInshop = this.isGlassServiceableInshop =
serviceabilityDetails.isGlassServiceableInshop; serviceabilityDetails.isGlassServiceableInshop;
@ -537,14 +520,94 @@ export default {
serviceabilityDetails.isGlassServiceableMobile; serviceabilityDetails.isGlassServiceableMobile;
this.isRecalibrationServiceableMobile = this.isRecalibrationServiceableMobile =
serviceabilityDetails.isRecalibrationServiceableMobile; serviceabilityDetails.isRecalibrationServiceableMobile;
if (!this.isServiceableMobile) {
this.selectedAppointmentType = AppointmentTypeStrings.IN_SHOP;
}
},
async updateMobileZip() {
this.mobileZipError = '';
const zipCodeData = await getMobileZipCodeData(this.mobileZipCode);
if (!zipCodeData.isValid) {
this.mobileZipError = errorMessages.INVALID_ZIP;
} else if (!zipCodeData.isServiceable) {
this.mobileZipError = errorMessages.NO_SERVICE_IN_AREA(toTitleCase(zipCodeData.city));
} else {
this.city = zipCodeData.city;
this.zipCode = this.mobileZipCode;
this.setCtuForMobile(zipCodeData.zipCodeCtu);
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);
const serviceabilityDetailsPromise = getServiceabilityDetails(this.mobileZipCode);
serviceabilityDetailsPromise.then((result) => {
const details = result.data;
if (details) {
this.setServiceabilityDetails(details);
}
});
const providersPromise = useMainStore().getProviders(this.mobileZipCode);
providersPromise.then((result) => {
const providers = result.data;
if (providers) {
this.setMobileProviderNumber(providers.mobileProviderNumber);
if(providers.shopProviders.length > 0) {
this.selectedProvider = providers.shopProviders[0];
}
else {
this.selectedProvider = null;
}
}
});
}
},
async handleInShopZipUpdated(newZip) {
this.zipCode = newZip;
const zipCodeData = await getZipCodeData(this.zipCode);
this.city = zipCodeData.city;
this.setCtuForMobile(zipCodeData.zipCodeCtu);
this.setContainsMilitaryBase(zipCodeData.containsMilitaryBase);
const serviceabilityDetailsPromise = getServiceabilityDetails(this.zipCode);
serviceabilityDetailsPromise.then((result) => {
const details = result.data;
if (details) {
this.setServiceabilityDetails(details);
}
});
}
},
watch: {
mobileZipCode(newZip) {
if(newZip !== '') {
this.updateMobileZip();
}
},
selectedAppointmentType() {
this.mobileZipCode = '';
if(this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP && this.selectedProvider) {
const startDate = new Date();
const endDate = new Date();
endDate.setDate(startDate.getDate() + 6);
const formattedStartDate = startDate.toISOString().split('T')[0];
const formattedEndDate = endDate.toISOString().split('T')[0];
getAvailabilityRating(
formattedStartDate,
formattedEndDate,
AppointmentTypeStrings.IN_SHOP,
this.selectedProvider ? this.selectedProvider.providerNumber : null
).then((rating) => {
this.availabilityRating = rating;
});
}
} }
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
$page-side-padding: 1.5rem; $page-side-padding: 1.5rem;
.iss-heritage-container-width { .iss-heritage-container-width {
.service-location-container { .service-location-container {
position: relative; position: relative;
@ -554,58 +617,41 @@ $page-side-padding: 1.5rem;
} }
} }
.question-text { .service-location-container {
span { .subheader {
line-height: 1.5rem; margin: 1.25rem 0;
text-align: center;
} }
} .form-test-error {
.list-card-content p { font-weight: $font-weight-bold;
&:first-of-type {
line-height: 1.5rem;
} }
&:not(:nth-of-type(1)) { .appointment-type-question-text {
line-height: 1.25rem; color: $black;
} font-weight: 600;
} margin-bottom: 1rem;
.choose-option { font-size: 1rem;
.button-question > div { line-height: 1.625rem;
&:first-of-type { flex-direction: column;
margin-bottom: 0.95rem; .recal-text {
line-height: 1.5rem; margin-bottom: 1.25rem;
font-weight: $font-weight-bold;
} }
} }
} .mobile-service-zip-question {
.button-question { :deep(.form-test-error span) {
.row.form-test-error { font-weight: $font-weight-bold;
line-height: 1.5rem;
padding-left: 0rem !important;
}
}
.appointment-type-question {
.row {
margin-top: 0;
.col-12 {
margin: 0 0 .5rem 0;
&:last-child {
margin-bottom: 0;
}
} }
} }
.question-text { .expandable-link-container {
margin: 0; margin-bottom: 1rem;
span {
padding: 1rem 0;
text-align: center;
}
} }
} .expandable-link {
.service-location-button-question { font-style: italic;
.question-text { font-weight: $font-weight-bold;
margin-top: 1.5rem; text-decoration: none;
color: $heritage-blue-primary;
span { line-height: 1.375rem;
text-align: center; &:hover {
text-decoration: underline;
} }
} }
} }

View file

@ -35,7 +35,7 @@
<script> <script>
import textLink from '@/ux-components/text-link/text-link.vue'; import textLink from '@/ux-components/text-link/text-link.vue';
import serviceZipQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue'; import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
import modal from '@/digital-components/modal/modal.vue'; import modal from '@/digital-components/modal/modal.vue';
import alert from '@/ux-components/alert/alert.vue'; import alert from '@/ux-components/alert/alert.vue';

View file

@ -1,51 +0,0 @@
<template>
<textboxQuestion
ref="zipInputTextQuestion"
v-model="value"
:cmsWidgetName="cmsWidgetName"
inputId="serviceZipCode"
questionAlignment="center"
cornerStyle="rounded"
mask="#####"
:displayQuestionText="false"
isRequired
validationRules="zip-required|zip-format" />
</template>
<script>
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
// Supporting Files
import { defineRule } from 'vee-validate';
import { required, regex } from '@/helpers/validation-rules';
import errorMessages from '@/constants/error-messages';
// Define Validation Rules
defineRule('zip-required', required(errorMessages.SERVICE_ZIP_REQUIRED));
defineRule('zip-format', regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.SERVICE_ZIP_FORMAT));
export default {
name: 'service-zip-question',
components: {
textboxQuestion
},
// TODO: Correct prop def.
props: {
modelValue: {
serviceZipCode: String
},
cmsWidgetName: String
},
emits: ['update:modelValue'],
computed: {
value: {
get() {
return this.modelValue;
},
set(newValue) {
this.$emit('update:modelValue', newValue);
}
}
}
};
</script>

View file

@ -1,10 +1,10 @@
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import serviceZipQuestion from '@/layouts/service-location/service-zip-modal-question/service-zip-question/service-zip-question.vue'; import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
describe('service-zip-question.vue', () => { describe('service-zip-question.vue', () => {
it('Should get the modelValue', async () => { it('Should get the modelValue', async () => {
// Arrange // Arrange
const text = 'test'; const text = '12345';
const wrapper = shallowMount(serviceZipQuestion, { const wrapper = shallowMount(serviceZipQuestion, {
props: { props: {
modelValue: text modelValue: text
@ -13,16 +13,16 @@ describe('service-zip-question.vue', () => {
}); });
// Act // Act
const modelValueText = wrapper.vm.value; const modelValueText = wrapper.vm.internalZipcode;
wrapper.vm.value = 'test also'; wrapper.vm.internalZipcode = '55555';
// Assert // Assert
expect(modelValueText).toEqual('test'); expect(modelValueText).toEqual('12345');
}); });
it('Should emit to set value', async () => { it('Should emit to set value', async () => {
// Arrange // Arrange
const text = 'test'; const text = '12345';
const wrapper = shallowMount(serviceZipQuestion, { const wrapper = shallowMount(serviceZipQuestion, {
props: { props: {
modelValue: text modelValue: text
@ -31,9 +31,10 @@ describe('service-zip-question.vue', () => {
}); });
// Act // Act
wrapper.vm.value = 'test also'; wrapper.vm.internalZipcode = '55555';
wrapper.vm.updateModelValue();
// Assert // Assert
expect(wrapper.emitted('update:modelValue')).toEqual([['test also']]); expect(wrapper.emitted('update:modelValue')).toEqual([['55555']]);
}); });
}); });

View file

@ -0,0 +1,74 @@
<template>
<textboxQuestion
ref="zipInputTextQuestion"
v-model="internalZipcode"
:cmsWidgetName="cmsWidgetName"
inputId="serviceZipCode"
cornerStyle="rounded"
mask="#####"
isRequired
:hasError="hasError"
@clickEvent="updateModelValue"
:validationRules="`zip-required|${cmsWidgetName}-zip-format`" />
</template>
<script>
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
// Supporting Files
import { defineRule } from 'vee-validate';
import { required, regex } from '@/helpers/validation-rules';
import errorMessages from '@/constants/error-messages';
// Define Validation Rules
defineRule('zip-required', required(errorMessages.SERVICE_ZIP_REQUIRED));
export default {
name: 'service-zip-question',
components: {
textboxQuestion
},
// TODO: Correct prop def.
props: {
modelValue: String,
cmsWidgetName: String,
serviceZipFormatErrorMessage: {
type: String,
default: errorMessages.SERVICE_ZIP_FORMAT
},
hasError: {
type: Boolean,
default: false
}
},
data() {
return {
internalZipcode: this.modelValue
}
},
emits: ['update:modelValue'],
methods: {
updateModelValue() {
this.$emit('update:modelValue', this.internalZipcode);
}
},
watch: {
modelValue(newValue) {
this.internalZipcode = newValue;
}
},
mounted() {
defineRule(`${this.cmsWidgetName}-zip-format`, regex(/^\d{5}$/, this.serviceZipFormatErrorMessage));
}
};
</script>
<style lang="scss" scoped>
.textbox-question.has-success {
:deep(input.form-control) {
border: $border-input;
&:focus {
border: $border-input-focus;
}
}
}
</style>

View file

@ -0,0 +1,411 @@
<template>
<transition
name="fade"
mode="out-in">
<div
class="shop-address"
aria-live="polite">
<div class="address-header">{{ questionText }}</div>
<div
v-if="modelValue"
class="current-address">
<div class="address-line">
<span class="shop-name">{{ displayedShopName }}</span>
<span class="shop-distance">{{ modelValue.distanceInMiles?.toFixed(2) }} mi</span>
</div>
<div class="address-line">{{ getProviderAddress(modelValue) }}</div>
<div class="address-line">
{{ getProviderCityZipState(modelValue) }}
</div>
</div>
<div
v-else
class="no-shops-found">
{{ `No shops found within 100 miles of ${internalZipcode}` }}
</div>
<buttonMain
class="more-shops-button"
:buttonText="showMoreShopsLinkText"
:variant="'primary'"
@clickEvent="openModal" />
<alert
class="alert-shop-distance"
v-if="modelValue?.distanceInMiles > 30"
ref="alertShopDistance"
cmsWidgetName="AlertShopDistanceWidget"
alertClass="alert-warning" />
<modal
:ref="modalName"
:headerText="modalHeaderText"
:onModalOpenedCallback="onModalOpened"
:footerButtonText="modalFooterText"
:modalPosition="modalPositions.center"
:allowInvalidSubmit="true"
@footerButtonEvent="updateSelectedProvider">
<googleMap
id="map"
class="mb-4"
:markers="providerAddresses"
:zipCode="internalZipcode" />
<dropdownQuestion
inputId="searchRadiusDropdown"
ref="searchRadiusQuestion"
v-model="searchRadiusInMiles"
:cmsWidgetName="searchRadiusQuestionWidgetName"
:variant="dropdownVariants.compact"
:options="searchRadiusOptions" />
<buttonQuestion
ref="shopListButtonQuestion"
v-model="internalProviderNumber"
buttonTypeString="shopListButton"
:buttonTypeObject="shopListButton"
class="radioQuestion"
:answers="nearbyShopsData"
groupName="chooseShop"
textPosition="text-start"
isRequired
validationRules="option-required"
:additionalButtonData="additionalButtonData" />
<span class="service-zip-prompt">
{{ serviceZipPrompt }}
</span>
<serviceZipQuestion
v-if="renderServiceZip"
:ref="SERVICE_ZIP_QUESTION_REF_NAME"
v-model="internalZipcode"
customInputId="serviceZipCode"
:cmsWidgetName="textboxQuestionWidgetName" />
<div
v-if="errorMessage"
ref="errorMessageDiv"
class="row my-1 form-test-error">
<span
class="d-inline-flex mt-0"
role="alert">{{ errorMessage }}</span>
</div>
</modal>
</div>
</transition>
</template>
<script>
// Components
import alert from '@/ux-components/alert/alert.vue';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import modal from '@/digital-components/modal/modal.vue';
import serviceZipQuestion from '@/layouts/service-location/service-zip-question/service-zip-question.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
import dropdownQuestion from '@/digital-components/dropdown-question/dropdown-question.vue';
import googleMap from '@/iss-components/google-map/google-map.vue';
// Supporting files
import baseMixin from '@/mixins/base-mixin.js';
import { toTitleCase } from '@/helpers/text-helper.js';
import { markRaw } from 'vue';
import { getAvailabilityRating } from '@/helpers/service-location-helper';
import { useMainStore } from '@/store';
import widgetFields from '@/constants/cms-widget-fields';
import { dropdownVariants, modalPositions } from '@/constants/component-variants';
import errorMessages from '@/constants/error-messages';
import { defineRule } from 'vee-validate';
import { required } from '@/helpers/validation-rules';
defineRule('option-required', required(errorMessages.PROVIDER_REQUIRED));
const SERVICE_ZIP_QUESTION_REF_NAME = 'serviceZipCodeQuestion';
export default {
name: 'shop-address',
components: {
alert,
buttonMain,
modal,
serviceZipQuestion,
buttonQuestion,
dropdownQuestion,
googleMap
},
mixins: [baseMixin],
props: {
modelValue: {
type: Object,
default: () => null
},
serviceZipcode: String,
cmsWidgetName: String,
validationRules: String,
modalWidgetName: String,
selectedAppointmentType: String,
},
data() {
return {
toTitleCase,
serviceZipCodeTextInputId: '',
internalZipcode: this.serviceZipcode,
internalProviderNumber: this.modelValue?.providerNumber,
searchRadiusQuestionWidgetName: 'SearchRadiusQuestionWidget',
textboxQuestionWidgetName: 'ChangeLocationZipQuestionWidget',
shopListButton: markRaw(shopListButton),
searchRadiusInMiles: "25",
nearbyShops: [],
modalPositions,
dropdownVariants,
renderServiceZip: true,
errorMessage: '',
openingModal: false,
SERVICE_ZIP_QUESTION_REF_NAME
};
},
emits: ['update:modelValue', 'zip-updated'],
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
},
showMoreShopsLinkText() {
return this.getCmsContent('ShowMoreShopsLinkWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
displayedShopName() {
if(this.modelValue.isSafeliteShop) {
return this.getCmsContent('SafeliteShopNameWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
}
return toTitleCase(this.modelValue.companyName);
},
modalHeaderText() {
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT).replaceAll('{custom:serviceZipcode}', this.internalZipcode);
},
modalFooterText() {
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.FOOTER_TEXT);
},
modalName() {
return this.modalWidgetName;
},
additionalButtonData() {
const startDate = new Date();
const endDate = new Date();
endDate.setDate(startDate.getDate() + 6);
const formattedStartDate = startDate.toISOString().split('T')[0];
const formattedEndDate = endDate.toISOString().split('T')[0];
return {
displayAvailabilityIndicators: true,
availabilityRatingCallback: getAvailabilityRating,
startDate: formattedStartDate,
endDate: formattedEndDate,
shopAppointmentType: "InshopOrDropoff"
};
},
serviceZipPrompt() {
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT_2);
},
nearbyShopsData() {
return this.nearbyShops.map((shopProvider) => {
const city = toTitleCase(shopProvider.address.city);
const distanceInMiles = shopProvider.distanceInMiles.toFixed(2);
return {
buttonLabel: city,
buttonLabelSubCopy: `${distanceInMiles} mi`,
buttonBodyCopy: this.getFullProviderAddress(shopProvider),
value: shopProvider.providerNumber
};
});
},
searchRadiusArray() {
const options = this.getCmsContent(this.searchRadiusQuestionWidgetName, widgetFields.INPUT_QUESTION_WIDGET.ANSWERS);
return options;
},
searchRadiusOptions() {
const optionsObj = {};
if(this.searchRadiusArray) {
this.searchRadiusArray.forEach(option => {
optionsObj[option.Name] = option.Text;
});
}
return optionsObj;
},
providerAddresses() {
return this.nearbyShops?.map((provider) => ({
title: toTitleCase(provider.companyName),
fullAddress: this.getFullProviderAddress(provider),
addressLines: [this.getProviderAddress(provider), this.getProviderCityZipState(provider)]
})) ?? [];
},
},
watch: {
async internalZipcode() {
if(this.openingModal) {
return;
}
const shopsUpdated = await this.updateShops();
if (shopsUpdated) {
await this.$nextTick();
this.$refs[SERVICE_ZIP_QUESTION_REF_NAME].internalZipcode = '';
}
},
async searchRadiusInMiles() {
if(this.openingModal) {
return;
}
await this.updateShops();
}
},
methods: {
async updateShops(autoExpand = false) {
this.errorMessage = '';
let result = await this.getNearbyShops(this.searchRadiusInMiles);
let currentSearchIndex = this.searchRadiusArray.findIndex(option => option.Name === this.searchRadiusInMiles);
while (autoExpand && result.length === 0 && currentSearchIndex < this.searchRadiusArray.length - 1) {
const newSearchRadius = this.searchRadiusArray[currentSearchIndex + 1].Name;
result = await this.getNearbyShops(newSearchRadius);
currentSearchIndex++;
}
if (result.length === 0) {
this.errorMessage = errorMessages.ZIP_CODE_NOT_SERVICED_FOR_VEHICLE;
}
else {
if (!result.find(shop => shop.providerNumber === this.internalProviderNumber)) {
this.internalProviderNumber = '';
await this.$nextTick();
}
this.nearbyShops = result;
this.searchRadiusInMiles = this.searchRadiusArray[currentSearchIndex].Name;
return true;
}
},
openModal() {
this.$refs[this.modalName].openModal();
},
closeModal() {
this.$refs[this.modalName].closeModal();
},
onModalOpened() {
this.openingModal = true;
this.internalZipcode = this.serviceZipcode;
this.searchRadiusInMiles = "25";
this.nearbyShops = [];
this.updateShops(true).then(() => {
this.internalProviderNumber = this.nearbyShops[0]?.providerNumber;
}).finally(() => {
this.$nextTick().then(() => {
this.openingModal = false;
});
});
this.forceServiceZipRerender();
},
async updateSelectedProvider() {
const selectedShop = this.nearbyShops.find(shop => shop.providerNumber === this.internalProviderNumber);
if (selectedShop) {
this.$emit('update:modelValue', selectedShop);
this.$emit('zip-updated', this.internalZipcode);
this.closeModal();
}
},
async getNearbyShops(radiusInMiles) {
const result = await useMainStore().getProviders(this.internalZipcode, radiusInMiles);
return result.data.shopProviders;
},
getFullProviderAddress(provider) {
const addressLine1 = this.getProviderAddress(provider);
const addressLine2 = this.getProviderCityZipState(provider);
const joinString =
addressLine1.length > 0 && addressLine2.length > 0 ? ', ' : '';
return [addressLine1, addressLine2].join(joinString);
},
getProviderAddress(provider) {
return toTitleCase(provider?.address?.streetAddress);
},
getProviderCityZipState(provider) {
const city = toTitleCase(provider?.address?.city);
const state = provider?.address?.state ?? '';
const zipCode = provider?.address?.zipCode ?? '';
let addressLine2 = '';
if (city) {
addressLine2 += city;
if (state || zipCode) {
addressLine2 += state ? ', ' : ' ';
}
}
if (state) {
addressLine2 += state;
addressLine2 += zipCode ? ' ' : '';
}
if (zipCode) {
addressLine2 += zipCode;
}
return addressLine2;
},
forceServiceZipRerender() {
this.renderServiceZip = false;
this.$nextTick(() => {
this.renderServiceZip = true;
});
},
}
};
</script>
<style lang="scss" scoped>
@import "@/styles/ux-variables-svg-strings.scss";
.shop-address {
.address-header {
color: $black;
font-weight: 600;
margin-bottom: 0.625rem;
}
.address-line {
display: flex;
}
.shop-distance {
margin-left: auto;
}
.current-address {
margin: .625rem 0;
}
.no-shops-found {
font-size: .75rem;
margin: .75rem 0rem;
font-weight: $font-weight-bold;
line-height: 1.375rem;
}
.more-shops-button {
margin-bottom: 1.25rem;
}
.alert-shop-distance {
margin-top: 2.5rem;
}
:deep(.modal-title) {
font-weight: 400;
display: block;
width: fit-content;
}
.radioQuestion {
margin-bottom: .625rem;
}
.service-zip-prompt {
color: $black;
font-weight: 600;
}
:deep(#map) {
height: 27rem;
}
:deep(.textbox-question) {
&.has-error {
input.form-control {
border: $border-input;
&:focus {
border: $border-input-focus;
}
}
}
.form-label {
font-weight: $font-weight-normal;
color: $darker-gray;
}
.btn-primary {
margin-bottom: 1.25rem;
}
}
}
</style>

View file

@ -1,294 +0,0 @@
<template>
<transition
name="fade"
mode="out-in">
<div
v-if="isDisplayed"
class="shop-question"
aria-live="polite">
<alert
v-if="displayDropoffInformation"
ref="alertDropoffInformation"
class="mb-4 drop-off-alert"
cmsWidgetName="AlertDropoffInformationWidget"
alertClass="alert-info"
:isDismissible="false" />
<buttonQuestion
ref="buttonQuestion"
v-model="selectedProviderNumber"
buttonTypeString="shopListButton"
:buttonTypeObject="shopListButton"
class="radioQuestion"
:questionText="questionText"
:answers="answers"
groupName="chooseShop"
textPosition="text-start"
isRequired
validationRules="option-required"
:additionalButtonData="additionalButtonData" />
<textLink
v-show="displaySeeMoreLocationsLink"
id="showMoreShopsId"
ref="showMoreShopsLink"
class="show-more-shops-link"
cmsWidgetName="ShowMoreShopsLinkWidget"
linkType="text"
:text="showMoreShopsLinkText"
href="#!"
:aria-label="showMoreShopsLinkText"
@clickEvent="getNextShopsFromList" />
</div>
</transition>
</template>
<script>
// Components
import alert from '@/ux-components/alert/alert.vue';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
// Supporting files
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
import baseMixin from '@/mixins/base-mixin.js';
import { defineRule } from 'vee-validate';
import { required } from '@/helpers/validation-rules';
import errorMessages from '@/constants/error-messages';
import { useMainStore } from '@/store/index.js';
import { markRaw, nextTick } from 'vue';
import { getAvailabilityRating } from '@/helpers/service-location-helper';
import shopListButton from '@/iss-components/shop-list-button/shop-list-button.vue';
defineRule('option-required', required(errorMessages.OPTION_REQUIRED));
export default {
name: 'shop-question',
components: {
alert,
buttonQuestion,
textLink
},
mixins: [baseMixin],
props: {
modelValue: {
type: Object,
default: () => null
},
selectedAppointmentType: String,
cmsWidgetName: String,
validationRules: String,
isDisplayed: Boolean
},
emits: ['update:modelValue', 'updatedMobileProviderNumber'],
data() {
return {
shopProviders: [],
shopListButton: markRaw(shopListButton),
answers: [],
shopIndex: 0,
displaySeeMoreLocationsLink: false
};
},
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
selectedValue: {
get() {
return this.modelValue;
},
set(newValue) {
this.$emit('update:modelValue', newValue);
}
},
selectedProviderNumber: {
get() {
return this.selectedValue?.providerNumber;
},
set(newValue) {
// Button Question only supports Number, or String data types so we must get the full object to emit
this.selectedValue = this.getSelectedProviderObject(newValue);
}
},
displayDropoffInformation() {
return this.selectedAppointmentType === 'Dropoff';
},
showMoreShopsLinkText() {
return this.getCmsContent('ShowMoreShopsLinkWidget', 'Text');
},
additionalButtonData() {
const startDate = new Date();
const endDate = new Date();
endDate.setDate(startDate.getDate() + 6);
const formattedStartDate = startDate.toISOString().split('T')[0];
const formattedEndDate = endDate.toISOString().split('T')[0];
return {
displayAvailabilityIndicators: true,
availabilityRatingCallback: getAvailabilityRating,
startDate: formattedStartDate,
endDate: formattedEndDate,
shopAppointmentType: this.selectedAppointmentType
};
}
},
watch: {
selectedAppointmentType: {
async handler(newValue) {
this.resetAnswers();
await nextTick();
this.selectedProviderNumber = null;
await nextTick();
if (newValue !== AppointmentTypeStrings.MOBILE && newValue !== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
this.getNextShopsFromList();
}
}
},
shopProviders: {
async handler(newValue) {
await nextTick();
if (this.selectedAppointmentType) {
const selectedShopIndex = this.getSelectedProviderIndex(
newValue,
this.selectedProviderNumber
);
if (selectedShopIndex >= 3) {
await this.getNextShopsFromList(selectedShopIndex + 1);
} else {
await this.getNextShopsFromList();
await nextTick();
}
}
}
}
},
methods: {
loadInitialData(serviceZipCode) {
return this.loadData(serviceZipCode);
},
loadData(serviceZipCode) {
return useMainStore().getProviders(serviceZipCode);
},
initializeComponent(shopQuestionInitialData) {
this.shopProviders = shopQuestionInitialData.shopProviders;
},
async getNextShopsFromList(numberToGet = 3) {
const shopIterator = (array, n) => {
const l = array.length;
return () => {
const end = this.shopIndex + n;
const part = array.slice(this.shopIndex, end);
this.shopIndex = end < l ? end : this.shopProviders.length;
return part;
};
};
const toTitleCase = (str) => str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
const nextShop = shopIterator(this.shopProviders, numberToGet);
// Map API result data
const mappedData = nextShop().map((shopProvider) => {
const streetAddress = toTitleCase(shopProvider.address.streetAddress);
const city = toTitleCase(shopProvider.address.city);
const { state } = shopProvider.address;
const { zipCode } = shopProvider.address;
const distanceInMiles = Math.round(shopProvider.distanceInMiles * 2) / 2;
return {
buttonLabel: city,
buttonLabelSubCopy: `${distanceInMiles} mi`,
buttonBodyCopy: `${streetAddress}, ${city}, ${state} ${zipCode}`,
value: shopProvider.providerNumber
};
});
if (this.answers.length === 0) {
this.answers = mappedData;
} else {
mappedData.forEach((shop) => {
this.answers.push(shop);
});
}
await nextTick();
if (this.shopIndex === this.shopProviders.length) {
this.displaySeeMoreLocationsLink = false;
} else {
this.displaySeeMoreLocationsLink = true;
}
await nextTick();
this.scrollToPageBottom();
},
resetAnswers() {
this.answers = [];
this.shopIndex = 0;
},
async reloadShopData(serviceZipCode) {
const result = await this.loadData(serviceZipCode);
this.initializeComponent(result.data);
if (result.data?.mobileProviderNumber
&& (this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)) {
this.$emit('updatedMobileProviderNumber', result.data.mobileProviderNumber);
}
this.resetAnswers();
await nextTick();
await this.getNextShopsFromList();
},
getSelectedProviderObject(providerNumber) {
const provider = this.shopProviders?.find((p) => p.providerNumber === providerNumber);
return provider;
},
getSelectedProviderIndex(providers, selectedProviderNumber) {
const index = providers.findIndex((p) => p.providerNumber === selectedProviderNumber);
return index;
}
}
};
</script>
<style lang="scss">
@import "@/styles/ux-variables-svg-strings.scss";
.shop-question {
margin-top: 1rem;
text-align: center;
.button-question {
.question-text {
margin-top: 0.5rem;
}
}
.drop-off-alert {
background-image: url($svg-drop-off-alert);
background-repeat: no-repeat;
background-size: 0.75rem;
background-position: 0.5rem 0.75rem;
border-radius: 0.5rem;
display: flex;
flex-direction: row;
padding: 0.5rem 0.5rem 0.5rem 1.5rem !important;
gap: 0.25rem;
.alert-heading {
text-align: left;
font-size: 0.75rem;
line-height: 1.25rem;
}
}
}
</style>

View file

@ -2,24 +2,20 @@
exports[`tpa-submit returns the initial data 1`] = ` exports[`tpa-submit returns the initial data 1`] = `
Object { Object {
"companyName": "Frederick Jones", "modalPositions": Object {
"customValueMap": Object { "center": "center",
"glassShop": "Frederick Jones", "edge": "edge",
}, },
"sections": Array [], "sections": Array [],
"widget": Object { "widget": Object {
"damageLocations": "DamageLocationsWidget", "alertIncomplete": "AlertIncompleteWidget",
"alertRecalWarning": "AlertRecalWarningWidget",
"contactDetails": "ContactDetailsSectionWidget",
"editShopLinkText": "EditShopLinkTextWidget",
"footer": "SiteFooterWidget", "footer": "SiteFooterWidget",
"orderDetails": "OrderDetailsContent", "orderDetails": "OrderDetailsContent",
"serviceSummary": "ServiceSummaryContent",
"siteHeader": "SiteHeaderWidget", "siteHeader": "SiteHeaderWidget",
"siteSubHeader": "SiteSubHeaderWidget", "siteSubHeader": "SiteSubHeaderWidget",
"subheader": Object {
"contactInfo": "ContactDetailsSubTitle",
"damage": "DamageSubTitle",
"shop": "PreferredShopSubTitle",
"vehicle": "VehicleSubTitle",
},
}, },
} }
`; `;

View file

@ -3,12 +3,18 @@
exports[`contact-details-drawer snapshot matches returns the initial data 1`] = ` exports[`contact-details-drawer snapshot matches returns the initial data 1`] = `
Object { Object {
"emailAddress": "fred.tay@gmail.com", "emailAddress": "fred.tay@gmail.com",
"extension": null,
"firstName": "Frederick", "firstName": "Frederick",
"isModalOpened": false, "isModalOpened": false,
"lastName": "Taylor", "lastName": "Taylor",
"modalPositions": Object {
"center": "center",
"edge": "edge",
},
"phoneNumber": "606-009-2943", "phoneNumber": "606-009-2943",
"rules": Object { "rules": Object {
"emailAddress": "email-required|email-address-format", "emailAddress": "email-required|email-address-format",
"extension": "extension-format",
"firstName": "first-name-required", "firstName": "first-name-required",
"lastName": "last-name-required", "lastName": "last-name-required",
"phoneNumber": "phone-number-required|phone-number-format", "phoneNumber": "phone-number-required|phone-number-format",
@ -16,6 +22,7 @@ Object {
"widget": Object { "widget": Object {
"drawerFooter": "ContactDetailsDrawerFooterWidget", "drawerFooter": "ContactDetailsDrawerFooterWidget",
"emailQuestion": "EmailQuestionWidget", "emailQuestion": "EmailQuestionWidget",
"extensionQuestion": "ExtensionQuestionWidget",
"firstNameQuestion": "FirstNameQuestionWidget", "firstNameQuestion": "FirstNameQuestionWidget",
"lastNameQuestion": "LastNameQuestionWidget", "lastNameQuestion": "LastNameQuestionWidget",
"phoneNumberQuestion": "PhoneNumberQuestionWidget", "phoneNumberQuestion": "PhoneNumberQuestionWidget",

View file

@ -65,15 +65,16 @@ describe('contact-details-drawer', () => {
describe('method', () => { describe('method', () => {
describe('saveContactDetails', () => { describe('saveContactDetails', () => {
test.each([ test.each([
['Sarah', 'Jones', 's.jones@gmail.com', '724-996-0909'], ['Sarah', 'Jones', 's.jones@gmail.com', '724-996-0909', '12345'],
[null, 'Jones', 's.jones@gmail.com', '724-996-0909'], [null, 'Jones', 's.jones@gmail.com', '724-996-0909', '12345'],
['Sarah', null, 's.jones@gmail.com', '724-996-0909'], ['Sarah', null, 's.jones@gmail.com', '724-996-0909', '12345'],
['Sarah', 'Jones', null, '724-996-0909'], ['Sarah', 'Jones', null, '724-996-0909', '12345'],
['Sarah', 'Jones', 's.jones@gmail.com', null] ['Sarah', 'Jones', 's.jones@gmail.com', null, '12345'],
['Sarah', 'Jones', 's.jones@gmail.com', '724-996-0909', null],
])( ])(
'when first name data "%p", last name "%p", email "%p", and phone number "%p", updateContactInfo called with expected', 'when first name data "%p", last name "%p", email "%p", phone number "%p", and extension "%p", updateContactInfo called with expected',
(firstName, lastName, emailAddress, phoneNumber) => { (firstName, lastName, emailAddress, phoneNumber, extension) => {
// Arrange // Arrange
const mainInitialState = { const mainInitialState = {
order: { order: {
@ -81,12 +82,13 @@ describe('contact-details-drawer', () => {
firstName: 'Frederick', firstName: 'Frederick',
lastName: 'Taylor', lastName: 'Taylor',
emailAddress: 'fred.tay@gmail.com', emailAddress: 'fred.tay@gmail.com',
servicePhone: '606-009-2943' servicePhone: '606-009-2943',
extension: '11111'
} }
} }
}; };
const initialData = { const initialData = {
firstName, lastName, emailAddress, phoneNumber firstName, lastName, emailAddress, phoneNumber, extension
}; };
const { wrapper } = getMountedComponent(mainInitialState, initialData); const { wrapper } = getMountedComponent(mainInitialState, initialData);
@ -98,7 +100,7 @@ describe('contact-details-drawer', () => {
expect(useMainStore().updateContactInfo).toBeCalledTimes(1); expect(useMainStore().updateContactInfo).toBeCalledTimes(1);
expect(useMainStore().updateContactInfo).toBeCalledWith({ firstName, lastName, emailAddress }); expect(useMainStore().updateContactInfo).toBeCalledWith({ firstName, lastName, emailAddress });
expect(useMainStore().updatePhoneNumbers).toBeCalledTimes(1); expect(useMainStore().updatePhoneNumbers).toBeCalledTimes(1);
expect(useMainStore().updatePhoneNumbers).toBeCalledWith({ home: phoneNumber, service: phoneNumber }); expect(useMainStore().updatePhoneNumbers).toBeCalledWith({ home: phoneNumber, service: phoneNumber, extension: extension });
} }
); );
}); });

View file

@ -1,6 +1,7 @@
<template> <template>
<modal <modal
:ref="modalName" :ref="modalName"
:modalPosition="modalPositions.center"
:headerText="headerText" :headerText="headerText"
:footerButtonText="footerButtonText" :footerButtonText="footerButtonText"
:onModalClosedCallback="resetFormValues" :onModalClosedCallback="resetFormValues"
@ -8,25 +9,27 @@
@isModalOpened="setModalStatus" @isModalOpened="setModalStatus"
@footerButtonEvent="clickFooterButtonEvent"> @footerButtonEvent="clickFooterButtonEvent">
<template v-if="isModalOpened"> <template v-if="isModalOpened">
<textboxQuestion <div class="row">
ref="firstNameQuestion" <textboxQuestion
v-model="firstName" class="col"
inputId="firstName" ref="firstNameQuestion"
:cmsWidgetName="widget.firstNameQuestion" v-model="firstName"
isRequired inputId="firstName"
:validationRules="rules.firstName" /> :cmsWidgetName="widget.firstNameQuestion"
<textboxQuestion isRequired
ref="lastNameQuestion" :validationRules="rules.firstName" />
v-model="lastName" <textboxQuestion
class="mt-4" class="col"
inputId="lastName" ref="lastNameQuestion"
:cmsWidgetName="widget.lastNameQuestion" v-model="lastName"
isRequired inputId="lastName"
:validationRules="rules.lastName" /> :cmsWidgetName="widget.lastNameQuestion"
isRequired
:validationRules="rules.lastName" />
</div>
<textboxQuestion <textboxQuestion
ref="emailQuestion" ref="emailQuestion"
v-model="emailAddress" v-model="emailAddress"
class="mt-4"
inputId="emailAddress" inputId="emailAddress"
:cmsWidgetName="widget.emailQuestion" :cmsWidgetName="widget.emailQuestion"
isRequired isRequired
@ -34,13 +37,19 @@
<textboxQuestion <textboxQuestion
ref="phoneNumberQuestion" ref="phoneNumberQuestion"
v-model="phoneNumber" v-model="phoneNumber"
class="mt-4"
inputId="phoneNumber" inputId="phoneNumber"
:cmsWidgetName="widget.phoneNumberQuestion" :cmsWidgetName="widget.phoneNumberQuestion"
isRequired isRequired
:mask="phoneMask" :mask="phoneMask"
disableAutoFill disableAutoFill
:validationRules="rules.phoneNumber" /> :validationRules="rules.phoneNumber" />
<textboxQuestion
ref="extensionQuestion"
v-model="extension"
inputId="extension"
:cmsWidgetName="widget.extensionQuestion"
disableAutoFill
:validationRules="rules.extension" />
</template> </template>
</modal> </modal>
</template> </template>
@ -51,10 +60,11 @@ import modal from '@/digital-components/modal/modal.vue';
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue'; import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
// Supporting Files // Supporting Files
import { useMainStore } from '@/store/index.js'; import { useMainStore } from '@/store';
import globalRules from '@/constants/global-rules.js'; import globalRules from '@/constants/global-rules.js';
import MaskaFormattedMasks from '@/constants/maska-masks'; import MaskaFormattedMasks from '@/constants/maska-masks';
import widgetFields from '@/constants/cms-widget-fields.js'; import widgetFields from '@/constants/cms-widget-fields.js';
import { modalPositions } from '@/constants/component-variants';
export default { export default {
name: 'contact-details-drawer', name: 'contact-details-drawer',
@ -67,27 +77,32 @@ export default {
const { firstName, const { firstName,
lastName, lastName,
emailAddress, emailAddress,
servicePhone } = useMainStore().contactInfo; servicePhone,
extension } = useMainStore().contactInfo;
return { return {
isModalOpened: false, isModalOpened: false,
firstName, firstName,
lastName, lastName,
emailAddress, emailAddress,
phoneNumber: servicePhone, phoneNumber: servicePhone,
extension: extension,
widget: { widget: {
title: 'ContactDetailsDrawerHeaderWidget', title: 'ContactDetailsDrawerHeaderWidget',
firstNameQuestion: 'FirstNameQuestionWidget', firstNameQuestion: 'FirstNameQuestionWidget',
lastNameQuestion: 'LastNameQuestionWidget', lastNameQuestion: 'LastNameQuestionWidget',
emailQuestion: 'EmailQuestionWidget', emailQuestion: 'EmailQuestionWidget',
phoneNumberQuestion: 'PhoneNumberQuestionWidget', phoneNumberQuestion: 'PhoneNumberQuestionWidget',
extensionQuestion: 'ExtensionQuestionWidget',
drawerFooter: 'ContactDetailsDrawerFooterWidget' drawerFooter: 'ContactDetailsDrawerFooterWidget'
}, },
rules: { rules: {
firstName: globalRules.FIRST_NAME_REQUIRED, firstName: globalRules.FIRST_NAME_REQUIRED,
lastName: globalRules.LAST_NAME_REQUIRED, lastName: globalRules.LAST_NAME_REQUIRED,
emailAddress: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`, emailAddress: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}` phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`,
} extension: `${globalRules.EXTENSION_FORMAT}`
},
modalPositions
}; };
}, },
computed: { computed: {
@ -121,10 +136,11 @@ export default {
lastName: this.lastName, lastName: this.lastName,
emailAddress: this.emailAddress emailAddress: this.emailAddress
}; };
useMainStore().updateContactInfo(contactInfo); this.mainStore.updateContactInfo(contactInfo);
useMainStore().updatePhoneNumbers({ this.mainStore.updatePhoneNumbers({
home: this.phoneNumber, home: this.phoneNumber,
service: this.phoneNumber service: this.phoneNumber,
extension: this.extension
}); });
this.$emit('update-contact-details'); this.$emit('update-contact-details');
}, },
@ -132,11 +148,13 @@ export default {
const { firstName, const { firstName,
lastName, lastName,
emailAddress, emailAddress,
servicePhone } = useMainStore().contactInfo; servicePhone,
extension } = this.mainStore.contactInfo;
this.firstName = firstName; this.firstName = firstName;
this.lastName = lastName; this.lastName = lastName;
this.emailAddress = emailAddress; this.emailAddress = emailAddress;
this.phoneNumber = servicePhone; this.phoneNumber = servicePhone;
this.extension = extension;
}, },
openModal() { openModal() {
this.modal.openModal(); this.modal.openModal();
@ -155,5 +173,14 @@ export default {
font-size: $h5-font-size; font-size: $h5-font-size;
font-weight: $font-weight-normal !important; font-weight: $font-weight-normal !important;
} }
.textbox-question {
margin-bottom: 1.25rem;
:deep(label:has(b)) {
font-weight: $font-weight-normal;
b {
font-weight: 600;
}
}
}
} }
</style> </style>

View file

@ -18,7 +18,7 @@ describe('deductible-box', () => {
it('renders the correct deductible label', () => { it('renders the correct deductible label', () => {
const labelElement = wrapper.find('#deductibleLabel'); const labelElement = wrapper.find('#deductibleLabel');
expect(labelElement.text()).toBe('Deductible'); expect(labelElement.text()).toBe('Deductible:');
}); });
it('has the correct id', () => { it('has the correct id', () => {
@ -26,12 +26,8 @@ describe('deductible-box', () => {
}); });
it('has the correct classes', () => { it('has the correct classes', () => {
expect(wrapper.classes().length).toBe(5); expect(wrapper.classes().length).toBe(1);
expect(wrapper.classes()).toContain('deductible-box'); expect(wrapper.classes()).toContain('deductible-box');
expect(wrapper.classes()).toContain('d-flex');
expect(wrapper.classes()).toContain('justify-content-between');
expect(wrapper.classes()).toContain('align-items-center');
expect(wrapper.classes()).toContain('py-2');
}); });
it.each([ it.each([
['', ''], ['', ''],

View file

@ -1,17 +1,9 @@
<template> <template>
<div <div
id="deductibleBox" id="deductibleBox"
class="deductible-box d-flex justify-content-between align-items-center py-2"> class="deductible-box">
<p <span id="deductibleLabel" class="deductible-label">Deductible:</span>
id="deductibleLabel" <span id="deductibleValue" class="deductible-value">{{ value }}</span>
class="deductible-box__text">
Deductible
</p>
<p
id="deductibleValue"
class="deductible-box__text">
{{ value }}
</p>
</div> </div>
</template> </template>
@ -28,12 +20,17 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.deductible-box { .deductible-box {
background-color: $green; display: flex;
align-items: center;
gap: .25rem;
} }
.deductible-label {
.deductible-box__text { color: $black;
color: white; font-weight: 600;
}
.deductible-value {
color: $green;
font-weight: 500; font-weight: 500;
margin: 0; font-size: 1.625rem;
} }
</style> </style>

View file

@ -20,12 +20,12 @@ describe('ReviewBlock.vue', () => {
}); });
it('renders the correct number of lines', () => { it('renders the correct number of lines', () => {
const lineElements = wrapper.findAll('.review-block__body--line'); const lineElements = wrapper.findAll('.review-body p');
expect(lineElements.length).toBe(lines.length); expect(lineElements.length).toBe(lines.length);
}); });
it('renders the correct line text', () => { it('renders the correct line text', () => {
const lineElements = wrapper.findAll('.review-block__body--line'); const lineElements = wrapper.findAll('.review-body p');
lines.forEach((line, index) => { lines.forEach((line, index) => {
expect(lineElements.at(index).text()).toBe(line); expect(lineElements.at(index).text()).toBe(line);
}); });

View file

@ -1,29 +1,28 @@
<template> <template>
<div class="review-block"> <div class="review-block">
<div class="d-flex justify-content-between"> <p class="review-block__header">
<p class="review-block__header small-strong pb-1"> {{ customHeaderText }}
{{ customHeaderText }}
</p>
<textLink
linkType="textSmall"
:text="editLinkText"
useLoadingModal
href="javascript:void(0)"
class="link"
@clickEvent="editClicked">
<template
v-if="editScreenReaderTextCmsWidgetName"
#after-text>
<span class="sr-only"> {{ screenReaderOnlyText }} </span>
</template>
</textLink>
</div>
<p
v-for="line in lines"
:key="line"
class="small review-block__body--line"
v-html="line">
</p> </p>
<div class="review-body">
<p
v-for="line in lines"
:key="line"
v-html="line">
</p>
</div>
<textLink
linkType="text"
:text="editLinkText"
useLoadingModal
href="javascript:void(0)"
class="link"
@clickEvent="editClicked">
<template
v-if="editScreenReaderTextCmsWidgetName"
#after-text>
<span class="sr-only"> {{ screenReaderOnlyText }} </span>
</template>
</textLink>
</div> </div>
</template> </template>
@ -36,14 +35,13 @@ export default {
props: { props: {
customHeaderText: String, customHeaderText: String,
editScreenReaderTextCmsWidgetName: String, editScreenReaderTextCmsWidgetName: String,
lines: Array lines: Array,
editLinkText: {
type: String,
default: 'Edit'
},
}, },
emits: ['click-edit'], emits: ['click-edit'],
data() {
return {
editLinkText: 'Edit'
};
},
methods: { methods: {
editClicked() { editClicked() {
this.$emit('click-edit'); this.$emit('click-edit');
@ -59,12 +57,16 @@ export default {
} }
.review-block__header { .review-block__header {
color: $black; color: $black;
font-weight: $font-weight-bold;
margin-bottom: .625rem;
} }
.review-block__body--line {
color: $gray-600; .review-body {
margin-bottom: .625rem
} }
.link { .link {
text-underline-offset: 1px; font-weight: $font-weight-normal
} }
} }
</style> </style>

View file

@ -18,7 +18,15 @@ import coverageStatuses from '@/constants/coverage-statuses';
jest.mock('@/helpers/cms-content-helper', () => ({ jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn(), fetchCmsContentForPage: jest.fn(),
doesCopyContainRouterLink: jest.fn(), doesCopyContainRouterLink: jest.fn(),
getStringWithCustomValues: jest.fn(), getStringWithCustomValues: jest.fn((str, customValueMap) => {
let newString = str ?? '';
if (customValueMap != null) {
Object.keys(customValueMap).forEach((key) => {
newString = newString.replaceAll(`{custom:${key}}`, customValueMap[key]);
});
}
return newString;
}),
processIfStatements: jest.fn() processIfStatements: jest.fn()
})); }));
@ -129,11 +137,7 @@ describe('tpa-submit', () => {
// Assert // Assert
expect(subHeader.exists()).toBeTruthy(); expect(subHeader.exists()).toBeTruthy();
expect(subHeader.props().justifyText).toBe('center'); expect(subHeader.classes()).toContain('tpa-submit-title');
expect(subHeader.props().marginTopSizeOverride).toBe(4);
expect(subHeader.classes()).toContain('text-color--black');
expect(subHeader.classes()).toContain('fs-5');
expect(subHeader.classes()).toContain('tpa-submit__title--line-height');
}); });
test('sub header body one', () => { test('sub header body one', () => {
// Arrange // Arrange
@ -144,34 +148,6 @@ describe('tpa-submit', () => {
// Assert // Assert
expect(subHeaderBodyOne.exists()).toBeTruthy(); expect(subHeaderBodyOne.exists()).toBeTruthy();
expect(subHeaderBodyOne.classes()).toContain('small');
expect(subHeaderBodyOne.classes()).toContain('text-color--darker-gray');
});
test('sub header body two', () => {
// Arrange
const wrapper = shallowMount(tpaSubmit, getMountOptions());
// Act
const subHeaderBodyTwo = wrapper.findComponent({ ref: 'tpaSubmitSubHeaderBodyTwo' });
// Assert
expect(subHeaderBodyTwo.exists()).toBeTruthy();
expect(subHeaderBodyTwo.classes()).toContain('mb-4');
expect(subHeaderBodyTwo.classes()).toContain('small');
expect(subHeaderBodyTwo.classes()).toContain('text-color--darker-gray');
});
test('main button one', () => {
// Arrange
const wrapper = shallowMount(tpaSubmit, getMountOptions());
// Act
const mainButton = wrapper.findComponent({ ref: 'buttonMainOne' });
// Assert
expect(mainButton.exists()).toBeTruthy();
expect(mainButton.props().variant).toBe('success');
expect(mainButton.classes()).toContain('w-100');
expect(mainButton.classes()).toContain('mb-5');
}); });
test('service summary section', () => { test('service summary section', () => {
// Arrange // Arrange
@ -183,21 +159,6 @@ describe('tpa-submit', () => {
// Assert // Assert
expect(serviceSummarySection.exists()).toBeTruthy(); expect(serviceSummarySection.exists()).toBeTruthy();
}); });
test('service summary title', () => {
// Arrange
const wrapper = shallowMount(tpaSubmit, getMountOptions());
// Act
const serviceSummaryTitle = wrapper.findComponent({ ref: 'tpaSubmitServiceSummaryTitle' });
// Assert
expect(serviceSummaryTitle.exists()).toBeTruthy();
expect(serviceSummaryTitle.props().marginTopSizeOverride).toBe(4);
expect(serviceSummaryTitle.classes()).toContain('fw-bold');
expect(serviceSummaryTitle.classes()).toContain('fs-1');
expect(serviceSummaryTitle.classes()).toContain('lh-lg');
expect(serviceSummaryTitle.classes()).toContain('text-color--black');
});
describe('review blocks', () => { describe('review blocks', () => {
const title1 = 'Section 1'; const title1 = 'Section 1';
const title2 = 'Another Section'; const title2 = 'Another Section';
@ -253,24 +214,8 @@ describe('tpa-submit', () => {
// Assert // Assert
expect(submitOrderDetailsTitle.exists()).toBeTruthy(); expect(submitOrderDetailsTitle.exists()).toBeTruthy();
expect(submitOrderDetailsTitle.props().marginTopSizeOverride).toBe(4);
expect(submitOrderDetailsTitle.classes()).toContain('fw-bold'); expect(submitOrderDetailsTitle.classes()).toContain('fw-bold');
expect(submitOrderDetailsTitle.classes()).toContain('text-color--black'); expect(submitOrderDetailsTitle.classes()).toContain('order-details-title');
});
test('submit order details body', () => {
// Arrange
const wrapper = shallowMount(tpaSubmit, getMountOptions());
// Act
const submitOrderDetailsTitle = wrapper.findComponent({ ref: 'tpaSubmitOrderDetailsBody' });
// Assert
expect(submitOrderDetailsTitle.exists()).toBeTruthy();
expect(submitOrderDetailsTitle.props().marginTopSizeOverride).toBe(4);
expect(submitOrderDetailsTitle.classes()).toContain('mb-4');
expect(submitOrderDetailsTitle.classes()).toContain('px-4');
expect(submitOrderDetailsTitle.classes()).toContain('small');
expect(submitOrderDetailsTitle.classes()).toContain('text-color--darker-gray');
}); });
test('deductible box', () => { test('deductible box', () => {
// Arrange // Arrange
@ -306,7 +251,7 @@ describe('tpa-submit', () => {
}); });
}); });
describe('before route enter', () => { describe('before route enter', () => {
test('produces 4 sections', async () => { test('produces 2 sections', async () => {
// Arrange // Arrange
const { wrapper } = getMountedComponent(); const { wrapper } = getMountedComponent();
expect(wrapper.vm.sections.length).toBe(0); expect(wrapper.vm.sections.length).toBe(0);
@ -320,65 +265,13 @@ describe('tpa-submit', () => {
); );
// Assert // Assert
expect(wrapper.vm.sections.length).toBe(4); expect(wrapper.vm.sections.length).toBe(2);
});
test.each([
['2004', 'Honda', 'Civic', '2004 Honda Civic'],
[null, 'Honda', 'Civic', 'Honda Civic'],
['2004', '', 'Civic', '2004 Civic'],
['2004', 'Honda', null, '2004 Honda'],
['2004', null, undefined, '2004'],
[null, null, null, '']
])(
'when store vehicle has year %p, make %p, and model %p, has line %p',
async (year, make, model, line) => {
// Arrange
const initialStore = {
order: {
vehicle: { year, make, model }
}
};
const { wrapper } = getMountedComponent(initialStore);
const vehicleSectionIndex = 0;
const expectedLines = [line];
// Act
await tpaSubmit.beforeRouteEnter.call(
wrapper.vm,
{ query: { issPage: 'tpa-submit' } },
undefined,
(c) => c(wrapper.vm)
);
// Assert
const vehicleSection = wrapper.vm.sections[vehicleSectionIndex];
expect(vehicleSection.lines).toStrictEqual(expectedLines);
}
);
test('damage section lines equal result from getDamageDisplayContent', async () => {
// Arrange
const { wrapper } = getMountedComponent();
const damageSectionIndex = 1;
const expectedLines = ['hi', 'potato', 'vehicle 3'];
getDamageDisplayContent.mockImplementationOnce(() => expectedLines);
// Act
await tpaSubmit.beforeRouteEnter.call(
wrapper.vm,
{ query: { issPage: 'tpa-submit' } },
undefined,
(c) => c(wrapper.vm)
);
// Assert
const damageSection = wrapper.vm.sections[damageSectionIndex];
expect(damageSection.lines).toEqual(expectedLines);
}); });
describe('preferred shop section', () => { describe('preferred shop section', () => {
test('has three lines', async () => { test('has two lines', async () => {
// Arrange // Arrange
const { wrapper } = getMountedComponent(); const { wrapper } = getMountedComponent();
const preferredShopSectionIndex = 2; const preferredShopSectionIndex = 0;
// Act // Act
await tpaSubmit.beforeRouteEnter.call( await tpaSubmit.beforeRouteEnter.call(
@ -390,15 +283,15 @@ describe('tpa-submit', () => {
// Assert // Assert
const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex]; const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex];
expect(preferredShopSection.lines.length).toBe(3); expect(preferredShopSection.lines.length).toBe(2);
}); });
test('first line is value returned from toTitleCase method', async () => { test('title is value returned from toTitleCase method', async () => {
// Arrange // Arrange
const initialData = { companyName: 'some value' }; const initialData = { companyName: 'some value' };
const { wrapper } = getMountedComponent({}, initialData); const { wrapper } = getMountedComponent({}, initialData);
const expectedName = 'some expected name'; const expectedName = 'some expected name';
toTitleCase.mockImplementationOnce(() => expectedName); toTitleCase.mockImplementationOnce(() => expectedName);
const preferredShopSectionIndex = 2; const preferredShopSectionIndex = 0;
// Act // Act
await tpaSubmit.beforeRouteEnter.call( await tpaSubmit.beforeRouteEnter.call(
@ -410,9 +303,9 @@ describe('tpa-submit', () => {
// Assert // Assert
const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex]; const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex];
expect(preferredShopSection.lines[0]).toBe(expectedName); expect(preferredShopSection.title).toBe(expectedName);
}); });
test('second line is expected and formatAddress called', async () => { test('first line is expected and formatAddress called', async () => {
// Arrange // Arrange
const address = { const address = {
streetAddress: '123 South Ln', streetAddress: '123 South Ln',
@ -430,7 +323,7 @@ describe('tpa-submit', () => {
const { wrapper } = getMountedComponent(initialStore); const { wrapper } = getMountedComponent(initialStore);
const line = 'some returned line'; const line = 'some returned line';
formatAddress.mockImplementationOnce(() => line); formatAddress.mockImplementationOnce(() => line);
const preferredShopSectionIndex = 2; const preferredShopSectionIndex = 0;
// Act // Act
await tpaSubmit.beforeRouteEnter.call( await tpaSubmit.beforeRouteEnter.call(
@ -442,7 +335,7 @@ describe('tpa-submit', () => {
// Assert // Assert
const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex]; const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex];
expect(preferredShopSection.lines[1]).toBe(line); expect(preferredShopSection.lines[0]).toBe(line);
expect(formatAddress).toHaveBeenCalledTimes(1); expect(formatAddress).toHaveBeenCalledTimes(1);
expect(formatAddress).toHaveBeenCalledWith( expect(formatAddress).toHaveBeenCalledWith(
address.streetAddress, address.streetAddress,
@ -452,7 +345,7 @@ describe('tpa-submit', () => {
address.zipCode address.zipCode
); );
}); });
test('third line is expected and toDisplayPhoneNumber called', async () => { test('second line is expected and toDisplayPhoneNumber called', async () => {
// Arrange // Arrange
const phoneNumber = '9998887777'; const phoneNumber = '9998887777';
const initialStore = { const initialStore = {
@ -465,7 +358,7 @@ describe('tpa-submit', () => {
const { wrapper } = getMountedComponent(initialStore); const { wrapper } = getMountedComponent(initialStore);
const expectedLine = 'returned from to display phone num'; const expectedLine = 'returned from to display phone num';
toDisplayPhoneNumber.mockImplementationOnce(() => expectedLine); toDisplayPhoneNumber.mockImplementationOnce(() => expectedLine);
const preferredShopSectionIndex = 2; const preferredShopSectionIndex = 0;
// Act // Act
await tpaSubmit.beforeRouteEnter.call( await tpaSubmit.beforeRouteEnter.call(
@ -477,7 +370,7 @@ describe('tpa-submit', () => {
// Assert // Assert
const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex]; const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex];
expect(preferredShopSection.lines[2]).toBe(expectedLine); expect(preferredShopSection.lines[1]).toBe(expectedLine);
expect(toDisplayPhoneNumber).toHaveBeenCalledWith(phoneNumber); expect(toDisplayPhoneNumber).toHaveBeenCalledWith(phoneNumber);
}); });
}); });
@ -487,8 +380,14 @@ describe('tpa-submit', () => {
const lastName = 'Eddison'; const lastName = 'Eddison';
const emailAddress = 'myname@gmail.com'; const emailAddress = 'myname@gmail.com';
const servicePhone = '0001112222'; const servicePhone = '0001112222';
const providerCompanyName = 'Some Provider LLC';
const initialStore = { const initialStore = {
order: { order: {
serviceLocation: {
provider: {
companyName: providerCompanyName
}
},
contactInfo: { contactInfo: {
firstName, firstName,
lastName, lastName,
@ -498,11 +397,11 @@ describe('tpa-submit', () => {
} }
}; };
const { wrapper } = getMountedComponent(initialStore); const { wrapper } = getMountedComponent(initialStore);
const expectedLine1 = 'Jones Eddison'; const expectedEmail = emailAddress;
const expectedLine2 = emailAddress; const expectedPhone = 'some value returned';
const expectedLine3 = 'some value returned'; toDisplayPhoneNumber.mockImplementation((number) => (number === servicePhone ? expectedPhone : ''));
toDisplayPhoneNumber.mockImplementation((number) => (number === servicePhone ? expectedLine3 : '')); const contactInfoSectionIndex = 1;
const contactInfoSectionIndex = 3; wrapper.vm.getCmsContent.mockImplementation(() => '{custom:contactPhone} or {custom:contactEmail}');
// Act // Act
await tpaSubmit.beforeRouteEnter.call( await tpaSubmit.beforeRouteEnter.call(
@ -511,12 +410,59 @@ describe('tpa-submit', () => {
undefined, undefined,
(c) => c(wrapper.vm) (c) => c(wrapper.vm)
); );
wrapper.vm.setSections();
// Assert // Assert
const contactInfoSection = wrapper.vm.sections[contactInfoSectionIndex]; const contactInfoSection = wrapper.vm.sections[contactInfoSectionIndex];
expect(contactInfoSection.lines[0]).toBe(expectedLine1); expect(contactInfoSection.lines[0]).toContain(expectedEmail);
expect(contactInfoSection.lines[1]).toBe(expectedLine2); expect(contactInfoSection.lines[0]).toContain(expectedPhone);
expect(contactInfoSection.lines[2]).toBe(expectedLine3); expect(toDisplayPhoneNumber).toHaveBeenCalledWith(servicePhone);
});
test('contact info section has expected content with extension', async () => {
// Arrange
const firstName = 'Jones';
const lastName = 'Eddison';
const emailAddress = 'myname@gmail.com';
const servicePhone = '0001112222';
const extension = '12345';
const providerCompanyName = 'Some Provider LLC';
const initialStore = {
order: {
serviceLocation: {
provider: {
companyName: providerCompanyName
}
},
contactInfo: {
firstName,
lastName,
emailAddress,
servicePhone,
extension
}
}
};
const { wrapper } = getMountedComponent(initialStore);
const expectedEmail = emailAddress;
const expectedPhone = 'some value returned';
toDisplayPhoneNumber.mockImplementation((number) => (number === servicePhone ? expectedPhone : ''));
const contactInfoSectionIndex = 1;
wrapper.vm.getCmsContent.mockImplementation(() => '{custom:contactPhone} or {custom:contactEmail}');
// Act
await tpaSubmit.beforeRouteEnter.call(
wrapper.vm,
{ query: { issPage: 'tpa-submit' } },
undefined,
(c) => c(wrapper.vm)
);
wrapper.vm.setSections();
// Assert
const contactInfoSection = wrapper.vm.sections[contactInfoSectionIndex];
expect(contactInfoSection.lines[0]).toContain(expectedEmail);
expect(contactInfoSection.lines[0]).toContain(expectedPhone);
expect(contactInfoSection.lines[0]).toContain(`Ext. ${extension}`);
expect(toDisplayPhoneNumber).toHaveBeenCalledWith(servicePhone); expect(toDisplayPhoneNumber).toHaveBeenCalledWith(servicePhone);
}); });
}); });
@ -524,7 +470,6 @@ describe('tpa-submit', () => {
test.each([ test.each([
['subHeaderTitle', 'SiteSubHeaderWidget', widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT, 'site sub header'], ['subHeaderTitle', 'SiteSubHeaderWidget', widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT, 'site sub header'],
['subHeaderBodyOne', 'SiteSubHeaderWidget', widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT, 'sub header body one'], ['subHeaderBodyOne', 'SiteSubHeaderWidget', widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT, 'sub header body one'],
['serviceSummaryText', 'ServiceSummaryContent', widgetFields.TEXT_BLOCK_WIDGET.TEXT, 'service summary text'],
['orderDetailsTitle', 'OrderDetailsContent', widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT, 'order details title'], ['orderDetailsTitle', 'OrderDetailsContent', widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT, 'order details title'],
['forwardButtonText', 'SiteFooterWidget', widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT, 'forward button text'] ['forwardButtonText', 'SiteFooterWidget', widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT, 'forward button text']
])('computed %p returns expected value', (computedName, widgetLabel, fieldLabel, expected) => { ])('computed %p returns expected value', (computedName, widgetLabel, fieldLabel, expected) => {

View file

@ -13,53 +13,52 @@
</div> </div>
<div class="iss-heritage-container-width"> <div class="iss-heritage-container-width">
<div class="tpa-submit-container iss-heritage-content-container-width"> <div class="tpa-submit-container iss-heritage-content-container-width">
<!-- TODO fix styling --> <alert
ref="alertIncomplete"
alertClass="alert-warning"
:cmsWidgetName="widget.alertIncomplete"
/>
<textBlock <textBlock
ref="subHeaderTitle" ref="subHeaderTitle"
:customText="subHeaderTitle" :customText="subHeaderTitle"
justifyText="center" class="tpa-submit-title"/>
:marginTopSizeOverride="4"
class="text-color--black fs-5 tpa-submit__title--line-height" />
<textBlock <textBlock
id="tpaSubmitSubHeaderBodyOne" id="tpaSubmitSubHeaderBodyOne"
ref="tpaSubmitSubHeaderBodyOne" ref="tpaSubmitSubHeaderBodyOne"
:customText="subHeaderBodyOne" :customText="subHeaderBodyOne" />
class="small text-color--darker-gray" /> <alert
<textBlock v-if="recalibrationRequired"
id="tpaSubmitSubHeaderBodyTwo" ref="alertRecalWarning"
ref="tpaSubmitSubHeaderBodyTwo" alertClass="alert-warning recal-alert"
:customText="subHeaderBodyTwo" :isCollapsible="true"
class="mb-4 small text-color--darker-gray" /> :cmsWidgetName="widget.alertRecalWarning"
<buttonMain @textLinkClicked="openRecalModal" />
ref="buttonMainOne" <modal
variant="success" ref="recalModal"
:buttonText="forwardButtonText" class="recal-modal"
class="w-100 mb-5" :modalPosition="modalPositions.center"
@clickEvent="forwardButtonAction" /> :headerText="recalModalContent.headerText"
<hr class="mb-0" /> :footerButtonText="recalModalContent.footerButtonText"
@footerButtonEvent="closeRecalModal">
<div class="recal-modal-subheader">{{ recalModalContent.subHeaderText }}</div>
<img class="recal-modal-image" :src="recalModalContent.image" />
<div class="recal-modal-body" v-html="recalModalContent.bodyText"></div>
</modal>
<hr />
<div id="serviceSummarySection"> <div id="serviceSummarySection">
<textBlock <div
id="tpaSubmitServiceSummaryTitle" v-for="(section, index) in sections"
ref="tpaSubmitServiceSummaryTitle" :key="section.title">
:customText="serviceSummaryText" <hr v-if="index !== 0" />
:marginTopSizeOverride="4" <reviewBlock
class="fw-bold fs-1 lh-lg text-color--black" /> :id="'review-block-' + index"
<div class="px-4 my-3"> :customHeaderText="section.title"
<div :lines="section.lines"
v-for="(section, index) in sections" :editLinkText="section.editLinkText"
:key="section.title"> @clickEdit="() => section.onClickEdit()" />
<hr
v-if="index !== 0"
class="my-3" />
<reviewBlock
:id="'review-block-' + index"
:customHeaderText="section.title"
:lines="section.lines"
@clickEdit="() => section.onClickEdit()" />
</div>
</div> </div>
</div> </div>
<hr class="mb-0" /> <hr />
<div <div
id="submitOrderDetailsSection" id="submitOrderDetailsSection"
ref="submitOrderDetailsSection"> ref="submitOrderDetailsSection">
@ -67,17 +66,10 @@
id="tpaSubmitOrderDetailsTitle" id="tpaSubmitOrderDetailsTitle"
ref="tpaSubmitOrderDetailsTitle" ref="tpaSubmitOrderDetailsTitle"
:customText="orderDetailsTitle" :customText="orderDetailsTitle"
:marginTopSizeOverride="4" :marginTopSizeOverride="0"
class="fw-bold text-color--black" /> class="fw-bold order-details-title" />
<textBlock
id="tpaSubmitOrderDetailsBody"
ref="tpaSubmitOrderDetailsBody"
:customText="orderDetailsBody"
:marginTopSizeOverride="4"
class="mb-4 px-4 small text-color--darker-gray" />
<deductibleBox <deductibleBox
ref="deductibleBox" ref="deductibleBox"
class="px-3"
:value="deductibleBoxValue" /> :value="deductibleBoxValue" />
</div> </div>
<siteFooter <siteFooter
@ -104,6 +96,8 @@ import reviewBlock from '@/layouts/tpa-submit/review-block/review-block.vue';
import deductibleBox from '@/layouts/tpa-submit/deductible-box/deductible-box.vue'; import deductibleBox from '@/layouts/tpa-submit/deductible-box/deductible-box.vue';
import contactDetailsDrawer from '@/layouts/tpa-submit/contact-details-drawer/contact-details-drawer.vue'; import contactDetailsDrawer from '@/layouts/tpa-submit/contact-details-drawer/contact-details-drawer.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue'; import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import alert from '@/ux-components/alert/alert.vue';
import modal from '@/digital-components/modal/modal.vue';
// Supporting files // Supporting files
import { import {
@ -114,21 +108,16 @@ import {
import { Form } from 'vee-validate'; import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js'; import BaseFormMixin from '@/mixins/base-form-mixin.js';
import widgetFields from '@/constants/cms-widget-fields.js'; import widgetFields from '@/constants/cms-widget-fields.js';
import { useMainStore } from '@/store';
import { import {
toTitleCase, toTitleCase,
toDisplayPhoneNumber, toDisplayPhoneNumber,
formatAddress, formatAddress,
formatAmountInDollars formatAmountInDollars
} from '@/helpers/text-helper.js'; } from '@/helpers/text-helper.js';
import {
getDamageDisplayContent,
getLocationAnswer
} from '@/helpers/damage-review-content-generator.js';
import damageLocationsSelected from '@/constants/damage-locations-selected.js';
import { submitWorkOrder } from '@/helpers/order-helper.js'; import { submitWorkOrder } from '@/helpers/order-helper.js';
import bailoutMessage from '@/constants/bailoutMessage'; import bailoutMessage from '@/constants/bailoutMessage';
import submitType from '@/constants/submit-type'; import submitType from '@/constants/submit-type';
import { modalPositions } from '@/constants/component-variants';
const VERIFYING_COVERAGE = 'Verifying coverage'; const VERIFYING_COVERAGE = 'Verifying coverage';
@ -143,7 +132,9 @@ export default {
siteFooter, siteFooter,
contactDetailsDrawer, contactDetailsDrawer,
// eslint-disable-next-line vue/no-reserved-component-names // eslint-disable-next-line vue/no-reserved-component-names
Form Form,
alert,
modal
}, },
mixins: [BaseFormMixin], mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -154,27 +145,19 @@ export default {
}); });
}, },
data() { data() {
const { companyName } = useMainStore().order.serviceLocation.provider;
return { return {
sections: [], sections: [],
widget: { widget: {
siteHeader: 'SiteHeaderWidget', siteHeader: 'SiteHeaderWidget',
siteSubHeader: 'SiteSubHeaderWidget', siteSubHeader: 'SiteSubHeaderWidget',
serviceSummary: 'ServiceSummaryContent',
subheader: {
vehicle: 'VehicleSubTitle',
damage: 'DamageSubTitle',
shop: 'PreferredShopSubTitle',
contactInfo: 'ContactDetailsSubTitle'
},
damageLocations: 'DamageLocationsWidget',
orderDetails: 'OrderDetailsContent', orderDetails: 'OrderDetailsContent',
footer: 'SiteFooterWidget' footer: 'SiteFooterWidget',
alertIncomplete: 'AlertIncompleteWidget',
alertRecalWarning: 'AlertRecalWarningWidget',
editShopLinkText: 'EditShopLinkTextWidget',
contactDetails: 'ContactDetailsSectionWidget'
}, },
companyName, modalPositions
customValueMap: {
glassShop: companyName
}
}; };
}, },
computed: { computed: {
@ -197,12 +180,6 @@ export default {
); );
return getStringWithCustomValues(cmsContent, this.customValueMap); return getStringWithCustomValues(cmsContent, this.customValueMap);
}, },
serviceSummaryText() {
return this.getCmsContent(
this.widget.serviceSummary,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
orderDetailsTitle() { orderDetailsTitle() {
return this.getCmsContent( return this.getCmsContent(
this.widget.orderDetails, this.widget.orderDetails,
@ -227,53 +204,21 @@ export default {
); );
}, },
isVerified() { isVerified() {
return useMainStore().isVerified; return this.mainStore.isVerified;
}, },
currentDeductible() { currentDeductible() {
return useMainStore().order.currentDeductible; return this.mainStore.order.currentDeductible;
}, },
deductibleBoxValue() { deductibleBoxValue() {
return this.isVerified return this.isVerified
? formatAmountInDollars(this.currentDeductible) ? formatAmountInDollars(this.currentDeductible)
: VERIFYING_COVERAGE; : VERIFYING_COVERAGE;
}, },
getVehicleLines() { getPreferredShopTitle() {
const { year, make, model } = useMainStore().order.vehicle; return toTitleCase(this.mainStore.order.serviceLocation.provider.companyName ?? '');
const line = [year, make, model]
.filter((v) => v != null && v !== '')
.join(' ');
return [line];
},
locationAnswers() {
return this.getInputQuestionWidgetAnswersNullSafe(this.widget.damageLocations);
},
driverSideDamageAnswers() {
const answerContent = getLocationAnswer(
damageLocationsSelected.DRIVER,
this.locationAnswers
);
return this.getInputQuestionWidgetAnswersNullSafe(answerContent?.SubWidgetName);
},
passengerSideDamageAnswers() {
const answerContent = getLocationAnswer(
damageLocationsSelected.PASSENGER,
this.locationAnswers
);
return this.getInputQuestionWidgetAnswersNullSafe(answerContent?.SubWidgetName);
},
getDamageLines() {
const { glassToReplace, isRepair } = useMainStore().order.damage;
return getDamageDisplayContent(
this.locationAnswers,
this.driverSideDamageAnswers,
this.passengerSideDamageAnswers,
glassToReplace,
isRepair
);
}, },
getPreferredShopLines() { getPreferredShopLines() {
const { phoneNumber, address } = const { phoneNumber, address } = this.mainStore.order.serviceLocation.provider;
useMainStore().order.serviceLocation.provider;
const { streetAddress, city, state, zipCode } = address; const { streetAddress, city, state, zipCode } = address;
const displayAddress = formatAddress( const displayAddress = formatAddress(
streetAddress, streetAddress,
@ -284,58 +229,86 @@ export default {
); );
const displayPhoneNumber = toDisplayPhoneNumber(phoneNumber); const displayPhoneNumber = toDisplayPhoneNumber(phoneNumber);
return [ return [
toTitleCase(this.companyName ?? ''),
displayAddress, displayAddress,
displayPhoneNumber displayPhoneNumber
]; ];
}, },
getEditShopLinkText() {
return this.getCmsContent(
this.widget.editShopLinkText,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
);
},
getContactInfoTitle() {
const cmsContent = this.getCmsContent(
this.widget.contactDetails,
widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT
);
return getStringWithCustomValues(cmsContent, this.customValueMap);
},
getContactInfoLines() { getContactInfoLines() {
const { firstName, lastName, emailAddress, servicePhone } = const cmsContent = this.getCmsContent(
useMainStore().contactInfo; this.widget.contactDetails,
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
);
return [ return [
`${firstName} ${lastName}`, getStringWithCustomValues(cmsContent, this.customValueMap)
emailAddress ?? '',
toDisplayPhoneNumber(servicePhone)
]; ];
},
getEditContactInfoLinkText() {
return this.getCmsContent(
this.widget.contactDetails,
widgetFields.CONTENT_GROUP_WIDGET.FOOTER_TEXT
);
},
recalModalContent() {
const widgetName = 'RecalModal';
return {
headerText: this.getCmsContent(widgetName, widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT),
subHeaderText: this.getCmsContent(widgetName, widgetFields.CONTENT_GROUP_WIDGET.SUBHEADER_TEXT),
bodyText: this.getCmsContent(widgetName, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT),
footerButtonText: this.getCmsContent(widgetName, widgetFields.CONTENT_GROUP_WIDGET.FOOTER_TEXT),
image: this.getCmsContent(widgetName, widgetFields.CONTENT_GROUP_WIDGET.IMAGE)
}
},
recalibrationRequired() {
return this.mainStore.hasRecalibrationPart;
},
customValueMap() {
let contactPhone = toDisplayPhoneNumber(this.mainStore.contactInfo.servicePhone);
if (this.mainStore.contactInfo.extension) {
contactPhone += ` Ext. ${this.mainStore.contactInfo.extension}`;
}
return {
glassShop: this.getPreferredShopTitle,
contactPhone: contactPhone,
contactEmail: this.mainStore.contactInfo.emailAddress ?? ''
}
} }
}, },
methods: { methods: {
setSections() { setSections() {
const vehicleScenario = useMainStore().isPolicyVehicle
? this.navigationScenarios.EDIT_POLICY_VEHICLE
: this.navigationScenarios.EDIT_VEHICLE;
this.sections = [ this.sections = [
this.getSection( this.getSection(
this.widget.subheader.vehicle, this.getPreferredShopTitle,
this.getVehicleLines,
() => this.navigate(vehicleScenario)
),
// eslint-disable-next-line max-len
this.getSection(
this.widget.subheader.damage,
this.getDamageLines,
() => this.navigate(this.navigationScenarios.EDIT_DAMAGE)
),
this.getSection(
this.widget.subheader.shop,
this.getPreferredShopLines, this.getPreferredShopLines,
() => this.navigate(this.navigationScenarios.EDIT_PREFERRED_SHOP) this.getEditShopLinkText,
() => this.navigate(this.navigationScenarios.EDIT_PREFERRED_SHOP),
), ),
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
this.getSection( this.getSection(
this.widget.subheader.contactInfo, this.getContactInfoTitle,
this.getContactInfoLines, this.getContactInfoLines,
this.getEditContactInfoLinkText,
this.openContactDetailsModal this.openContactDetailsModal
) )
]; ];
}, },
getSection(widgetName, lines, onClick) { getSection(title, lines, editLinkText, onClick) {
return { return {
title: this.getCmsContent( title: title,
widgetName,
widgetFields.TEXT_BLOCK_WIDGET.TEXT
),
lines, lines,
editLinkText,
onClickEdit: onClick onClickEdit: onClick
}; };
}, },
@ -344,7 +317,7 @@ export default {
await submitWorkOrder({ submitType: submitType.TPA }).then(() => { await submitWorkOrder({ submitType: submitType.TPA }).then(() => {
this.navigate(this.navigationScenarios.CLICKED_FORWARD); this.navigate(this.navigationScenarios.CLICKED_FORWARD);
}).catch((submitError) => { }).catch((submitError) => {
useMainStore().setBailout(bailoutMessage.saveSessionError(submitError.data)); this.mainStore.setBailout(bailoutMessage.saveSessionError(submitError.data));
this.navigate( this.navigate(
this.navigationScenarios.SAVE_SESSION_FAILED, this.navigationScenarios.SAVE_SESSION_FAILED,
this.$route, this.$route,
@ -372,6 +345,12 @@ export default {
}, },
openContactDetailsModal() { openContactDetailsModal() {
this.$refs.contactDetailsDrawer.openModal(); this.$refs.contactDetailsDrawer.openModal();
},
openRecalModal() {
this.$refs.recalModal.openModal();
},
closeRecalModal() {
this.$refs.recalModal.closeModal();
} }
} }
}; };
@ -387,26 +366,69 @@ export default {
} }
.tpa-submit { .tpa-submit {
#tpaSubmitOrderDetailsBody { .alert-warning {
p { margin-top: 1.25rem;
font-size: .875rem; margin-bottom: 0rem;
}
}
.tpa-submit__title--line-height {
line-height: map-get($spacers, 6);
} }
.text-color--darker-gray { .tpa-submit-title {
color: $darker-gray; font-size: 1.25rem;
color: $black;
font-weight: 400;
margin-top: 1.25rem;
margin-bottom: .625rem;
} }
.text-color--black { .recal-alert {
margin-bottom: 4rem;
}
b {
font-weight: $font-weight-bold;
color: $black; color: $black;
} }
hr { hr {
opacity: 1; margin: 1.875rem 0rem;
color: $gray-350; }
.fw-bold {
color: $black;
}
.order-details-title {
margin-bottom: .625rem;
}
.modal.recal-modal .modal-dialog {
.modal-header {
justify-content: start;
margin: 0rem;
padding: 1.25rem 1.25rem .625rem 1.25rem;
.modal-title {
font-weight: $font-weight-bolder;
font-size: .875rem;
line-height: 1.25rem;
text-align: start;
justify-content: start;
}
.btn-close {
display: none;
}
}
.modal-body {
padding: 0rem 1.25rem 1.25rem 1.25rem;
b {
font-weight: 600;
}
}
.recal-modal-subheader {
font-weight: $font-weight-bold;
color: $black;
}
.recal-modal-image {
margin: 1.25rem 0rem
}
} }
} }
</style> </style>

View file

@ -310,7 +310,14 @@ export default {
this.mainStore.updatePolicyData(this.welcomePageModel); this.mainStore.updatePolicyData(this.welcomePageModel);
const promises = []; const promises = [];
promises.push(this.configureZip().then(async () => await this.mainStore.getBillToInfo())); promises.push(this.configureZip().then(async () => await this.mainStore.getBillToInfo()));
if (!this.mainStore.order.loadedFromCookie) {
// Clear duplicate orders if navigating away from the welcome page after visiting duplicate check page.
if (this.mainStore.order.visitedDuplicateCheckPage) {
this.mainStore.clearDuplicateOrders();
}
// Skip duplicate check if loaded from cookie or already visited duplicate check page.
if (!this.mainStore.order.loadedFromCookie && !this.mainStore.order.visitedDuplicateCheckPage) {
promises.push(this.mainStore.getDuplicateReferrals()); promises.push(this.mainStore.getDuplicateReferrals());
} }
promises.push(this.mainStore.getCoveragePolicyInfo()); promises.push(this.mainStore.getCoveragePolicyInfo());
@ -472,6 +479,7 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '@/styles/ux-variables-svg-strings.scss';
form { form {
.container-fluid { .container-fluid {
display: flex; display: flex;
@ -489,6 +497,22 @@ form {
.form-group { .form-group {
margin-top: 1.25rem; margin-top: 1.25rem;
:deep(.input-wrapper) {
input[type="date"] {
max-height: 3rem;
}
}
&.has-error {
:deep(.input-wrapper) {
input[type="date"]::-webkit-calendar-picker-indicator {
background-color: $svg-calendar-error-fill-color;
// eslint-disable-next-line max-len
background-image: url($svg-error-calendar-graphic);
}
}
}
} }
} }
} }

View file

@ -27,6 +27,8 @@ const navigationScenarios = Object.freeze({
// Policy Holder Details // Policy Holder Details
CLICKED_FORWARD_POLICY_VERIFIED: 'CLICKED_FORWARD_POLICY_VERIFIED', CLICKED_FORWARD_POLICY_VERIFIED: 'CLICKED_FORWARD_POLICY_VERIFIED',
CLICKED_BACK_POLICY_UNVERIFIED: 'CLICKED_BACK_POLICY_UNVERIFIED',
CLICKED_BACK_POLICY_VERIFIED: 'CLICKED_BACK_POLICY_VERIFIED',
// Policy Vehicle // Policy Vehicle
CLICKED_FORWARD_LISTED_VEHICLE: 'CLICKED_FORWARD_LISTED_VEHICLE', CLICKED_FORWARD_LISTED_VEHICLE: 'CLICKED_FORWARD_LISTED_VEHICLE',

View file

@ -9,11 +9,11 @@ const routingTable = () => [
maps: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS destinationIssPageValue: issPageValues.WELCOME_PAGE
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT, scenario: navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
@ -26,7 +26,7 @@ const routingTable = () => [
maps: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.VEHICLE_SELECTION destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_REPAIR, scenario: navigationScenarios.CLICKED_FORWARD_WITH_REPAIR,
@ -386,7 +386,7 @@ const routingTable = () => [
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS destinationIssPageValue: issPageValues.VEHICLE_SELECTION
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_DUPLICATES, scenario: navigationScenarios.CLICKED_FORWARD_WITH_DUPLICATES,
@ -394,7 +394,7 @@ const routingTable = () => [
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED, scenario: navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS destinationIssPageValue: issPageValues.VEHICLE_SELECTION
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES, scenario: navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
@ -423,7 +423,7 @@ const routingTable = () => [
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED, scenario: navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS destinationIssPageValue: issPageValues.VEHICLE_SELECTION
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES, scenario: navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
@ -435,7 +435,7 @@ const routingTable = () => [
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_POLICY_VEHICLE, scenario: navigationScenarios.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_POLICY_VEHICLE,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_NON_POLICY_VEHICLE, scenario: navigationScenarios.CLICKED_FORWARD_LOADED_DUPLICATE_WITH_NON_POLICY_VEHICLE,
@ -451,16 +451,20 @@ const routingTable = () => [
issPageValue: issPageValues.POLICY_HOLDER_DETAILS, issPageValue: issPageValues.POLICY_HOLDER_DETAILS,
maps: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK_POLICY_UNVERIFIED,
destinationIssPageValue: issPageValues.VEHICLE_SELECTION
},
{
scenario: navigationScenarios.CLICKED_BACK_POLICY_VERIFIED,
destinationIssPageValue: issPageValues.WELCOME_PAGE destinationIssPageValue: issPageValues.WELCOME_PAGE
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED, scenario: navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
destinationIssPageValue: issPageValues.VEHICLE_SELECTION destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED, scenario: navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
destinationIssPageValue: issPageValues.POLICY_VEHICLES destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
} }
] ]
}, },
@ -473,7 +477,7 @@ const routingTable = () => [
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_LISTED_VEHICLE, scenario: navigationScenarios.CLICKED_FORWARD_LISTED_VEHICLE,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE, scenario: navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE,
@ -776,11 +780,11 @@ const routingTable = () => [
maps: [ maps: [
{ {
scenario: navigationScenarios.CLICKED_BACK, scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.POLICY_VEHICLES destinationIssPageValue: issPageValues.WELCOME_PAGE
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD, scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
}, },
{ {
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND, scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND,

View file

@ -19,7 +19,7 @@ describe('Router', () => {
router.push = jest.fn(); router.push = jest.fn();
router.navigate(scenario, currentRoute); router.navigate(scenario, currentRoute);
expect(router.push.mock.calls[0][0].query.issPage).toBe(issPageValues.POLICY_HOLDER_DETAILS); expect(router.push.mock.calls[0][0].query.issPage).toBe(issPageValues.VEHICLE_SELECTION);
}); });
it('Should set state as expected', () => { it('Should set state as expected', () => {

View file

@ -197,6 +197,7 @@ export const getDefaultState = () => ({
homePhone: null, homePhone: null,
alternativePhone: null, alternativePhone: null,
servicePhone: null, servicePhone: null,
extension: null,
requestTextUpdates: false, requestTextUpdates: false,
notesForTechnician: '' notesForTechnician: ''
}, },
@ -225,7 +226,8 @@ export const getDefaultState = () => ({
loadedFromCookie: false, loadedFromCookie: false,
loadedFromDupeCheck: null, loadedFromDupeCheck: null,
loadedSessionClearedPreviousData: null, loadedSessionClearedPreviousData: null,
availableVaps: null availableVaps: null,
visitedDuplicateCheckPage: false
}, },
applicationUser: { applicationUser: {
experiments: [], experiments: [],
@ -354,6 +356,7 @@ export const useMainStore = defineStore({
homePhone: s.order.contactInfo.homePhone, homePhone: s.order.contactInfo.homePhone,
alternativePhone: s.order.contactInfo.alternativePhone, alternativePhone: s.order.contactInfo.alternativePhone,
servicePhone: s.order.contactInfo.servicePhone, servicePhone: s.order.contactInfo.servicePhone,
extension: s.order.contactInfo.extension,
requestTextUpdates: s.order.contactInfo.requestTextUpdates ?? false, requestTextUpdates: s.order.contactInfo.requestTextUpdates ?? false,
notesForTechnician: s.order.contactInfo.notesForTechnician notesForTechnician: s.order.contactInfo.notesForTechnician
}), }),
@ -595,6 +598,12 @@ export const useMainStore = defineStore({
return Promise.reject(e); return Promise.reject(e);
} }
}, },
clearDuplicateOrders() {
this.applicationUser.duplicateOrders = [];
},
updateDuplicateCheckVisited(visited) {
this.order.visitedDuplicateCheckPage = visited;
},
updateCoverageStatus(status) { updateCoverageStatus(status) {
this.order.insuranceCoverage.coverageStatus = status; this.order.insuranceCoverage.coverageStatus = status;
}, },
@ -1012,14 +1021,13 @@ export const useMainStore = defineStore({
}); });
}, },
getProviders(serviceZipCode) { getProviders(serviceZipCode, shopRadiusInMiles = 100) {
const { carId, isBigTruck } = this.order.vehicle; const { carId, isBigTruck } = this.order.vehicle;
const damageType = this.damage.isRepair ? 'Repair' : 'Replace'; const damageType = this.damage.isRepair ? 'Repair' : 'Replace';
const { parentAccountNumber } = this.order; const { parentAccountNumber } = this.order;
const partsWithRecal = getTopLevelGlassPartsWithRecal(this.order.lineItems.glassParts); const partsWithRecal = getTopLevelGlassPartsWithRecal(this.order.lineItems.glassParts);
const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null; const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null;
const safeliteOnly = true; const safeliteOnly = true;
const shopRadiusInMiles = 100;
let url = `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}/${parentAccountNumber}/${safeliteOnly}/${carId}`; let url = `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}/${parentAccountNumber}/${safeliteOnly}/${carId}`;
if (windshieldPartWithRecal) { if (windshieldPartWithRecal) {
@ -1456,7 +1464,7 @@ export const useMainStore = defineStore({
emailAddress: contactInfo.emailAddress || customer.emailAddress, emailAddress: contactInfo.emailAddress || customer.emailAddress,
firstName: contactInfo.firstName || customer.firstName, firstName: contactInfo.firstName || customer.firstName,
lastName: contactInfo.lastName || customer.lastName, lastName: contactInfo.lastName || customer.lastName,
homePhone: contactInfo.homePhone, homePhone: contactInfo.extension && contactInfo.homePhone ? contactInfo.homePhone + contactInfo.extension : contactInfo.homePhone,
servicePhone: contactInfo.servicePhone, servicePhone: contactInfo.servicePhone,
alternativePhone: contactInfo.alternativePhone, alternativePhone: contactInfo.alternativePhone,
isSmsOptIn: contactInfo.requestTextUpdates ?? false isSmsOptIn: contactInfo.requestTextUpdates ?? false
@ -1590,7 +1598,8 @@ export const useMainStore = defineStore({
order.contactInfo.firstName = data?.customer?.firstName; order.contactInfo.firstName = data?.customer?.firstName;
order.contactInfo.lastName = data?.customer?.lastName; order.contactInfo.lastName = data?.customer?.lastName;
order.contactInfo.emailAddress = data?.customer?.emailAddress; order.contactInfo.emailAddress = data?.customer?.emailAddress;
order.contactInfo.homePhone = data?.customer?.homePhone; order.contactInfo.extension = data?.customer?.homePhone?.slice(10);
order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10);
order.contactInfo.servicePhone = data?.customer?.servicePhone; order.contactInfo.servicePhone = data?.customer?.servicePhone;
order.contactInfo.alternativePhone = data?.customer?.alternativePhone; order.contactInfo.alternativePhone = data?.customer?.alternativePhone;
order.contactInfo.requestTextUpdates = data?.customer?.isSmsOptIn; order.contactInfo.requestTextUpdates = data?.customer?.isSmsOptIn;
@ -1658,7 +1667,8 @@ export const useMainStore = defineStore({
order.contactInfo.firstName = data?.customer?.firstName; order.contactInfo.firstName = data?.customer?.firstName;
order.contactInfo.lastName = data?.customer?.lastName; order.contactInfo.lastName = data?.customer?.lastName;
order.contactInfo.emailAddress = data?.customer?.emailAddress; order.contactInfo.emailAddress = data?.customer?.emailAddress;
order.contactInfo.homePhone = data?.customer?.homePhone; order.contactInfo.extension = data?.customer?.homePhone?.slice(10);
order.contactInfo.homePhone = data?.customer?.homePhone?.slice(0, 10);
order.contactInfo.servicePhone = data?.customer?.servicePhone; order.contactInfo.servicePhone = data?.customer?.servicePhone;
order.contactInfo.alternativePhone = data?.customer?.alternativePhone; order.contactInfo.alternativePhone = data?.customer?.alternativePhone;
order.contactInfo.requestTextUpdates = data?.customer?.isSmsOptIn; order.contactInfo.requestTextUpdates = data?.customer?.isSmsOptIn;
@ -1720,6 +1730,7 @@ export const useMainStore = defineStore({
order.eon = data?.eon; order.eon = data?.eon;
order.workOrderId = data?.workOrderId; order.workOrderId = data?.workOrderId;
order.loadedFromCookie = true; order.loadedFromCookie = true;
order.visitedDuplicateCheckPage = true;
order.loadedSessionClearedPreviousData = false; order.loadedSessionClearedPreviousData = false;
issConfig.enableContinueFromCookie = false; issConfig.enableContinueFromCookie = false;
this.updateIsSafeliteProvider(data?.provider?.isSafeliteProvider); this.updateIsSafeliteProvider(data?.provider?.isSafeliteProvider);
@ -2042,6 +2053,7 @@ export const useMainStore = defineStore({
this.order.carrierPhoneNumber = null; this.order.carrierPhoneNumber = null;
this.order.customerPortalLoginToken = null; this.order.customerPortalLoginToken = null;
this.order.loadedFromCookie = false; this.order.loadedFromCookie = false;
this.order.visitedDuplicateCheckPage = false;
}, },
resetPolicy() { resetPolicy() {
@ -2096,6 +2108,7 @@ export const useMainStore = defineStore({
this.order.contactInfo.homePhone = null; this.order.contactInfo.homePhone = null;
this.order.contactInfo.alternativePhone = null; this.order.contactInfo.alternativePhone = null;
this.order.contactInfo.servicePhone = null; this.order.contactInfo.servicePhone = null;
this.order.contactInfo.extension = null;
this.order.contactInfo.requestTextUpdates = false; this.order.contactInfo.requestTextUpdates = false;
this.order.contactInfo.notesForTechnician = ''; this.order.contactInfo.notesForTechnician = '';
}, },
@ -2291,6 +2304,7 @@ export const useMainStore = defineStore({
this.order.customer.address.zipCode = customerQuestions.addressQuestions.zipCode; this.order.customer.address.zipCode = customerQuestions.addressQuestions.zipCode;
this.order.customer.firstName = customerQuestions.firstName; this.order.customer.firstName = customerQuestions.firstName;
this.order.customer.lastName = customerQuestions.lastName; this.order.customer.lastName = customerQuestions.lastName;
this.order.customer.emailAddress = customerQuestions.email;
this.order.serviceLocation.zipCode = customerQuestions.addressQuestions.zipCode; this.order.serviceLocation.zipCode = customerQuestions.addressQuestions.zipCode;
}, },
updateIsSafeliteProvider(isSafelite) { updateIsSafeliteProvider(isSafelite) {
@ -2576,6 +2590,7 @@ export const useMainStore = defineStore({
contact.homePhone = phoneNumbers.home !== undefined ? phoneNumbers.home : contact.homePhone; contact.homePhone = phoneNumbers.home !== undefined ? phoneNumbers.home : contact.homePhone;
contact.alternativePhone = phoneNumbers.alternative !== undefined ? phoneNumbers.alternative : contact.alternativePhone; contact.alternativePhone = phoneNumbers.alternative !== undefined ? phoneNumbers.alternative : contact.alternativePhone;
contact.servicePhone = phoneNumbers.service !== undefined ? phoneNumbers.service : contact.servicePhone; contact.servicePhone = phoneNumbers.service !== undefined ? phoneNumbers.service : contact.servicePhone;
contact.extension = phoneNumbers.extension !== undefined ? phoneNumbers.extension : contact.extension;
}, },
GetExperimentsByUser(userId) { GetExperimentsByUser(userId) {

View file

@ -494,18 +494,21 @@ describe('Store', () => {
const homePhone = getRandomInt(1000000000, 9999999999); const homePhone = getRandomInt(1000000000, 9999999999);
const servicePhone = getRandomInt(1000000000, 9999999999); const servicePhone = getRandomInt(1000000000, 9999999999);
const altPhone = getRandomInt(1000000000, 9999999999); const altPhone = getRandomInt(1000000000, 9999999999);
const extension = getRandomInt(10000, 99999);
// Act // Act
store.updatePhoneNumbers({ store.updatePhoneNumbers({
home: homePhone, home: homePhone,
service: servicePhone, service: servicePhone,
alternative: altPhone alternative: altPhone,
extension: extension
}); });
// Assert // Assert
expect(store.contactInfo.homePhone).toEqual(homePhone); expect(store.contactInfo.homePhone).toEqual(homePhone);
expect(store.contactInfo.servicePhone).toEqual(servicePhone); expect(store.contactInfo.servicePhone).toEqual(servicePhone);
expect(store.contactInfo.alternativePhone).toEqual(altPhone); expect(store.contactInfo.alternativePhone).toEqual(altPhone);
expect(store.contactInfo.extension).toEqual(extension);
}); });
it('All null values => contact info set in store to all nulls', () => { it('All null values => contact info set in store to all nulls', () => {
// Act // Act

View file

@ -9,7 +9,7 @@ body {
font-size: 16px; font-size: 16px;
letter-spacing: $letter-spacing-primary; letter-spacing: $letter-spacing-primary;
background-color: #fff; background-color: #fff;
color: #4d5151; color: $body-color;
.container-fluid { .container-fluid {
.prevent-squish { .prevent-squish {
@ -179,6 +179,10 @@ body {
} }
} }
} }
.site-sub-header-container {
margin-top: 1.25rem;
}
} }
:root { :root {

View file

@ -115,21 +115,3 @@ caption,
font-weight: 800; font-weight: 800;
font-style: normal; font-style: normal;
} }
@font-face {
font-family: "Urbanist";
src:
url("@/assets/fonts/Urbanist-MediumItalic.woff2") format("woff2"),
url("@/assets/fonts/Urbanist-MediumItalic.woff") format("woff");
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: "Urbanist";
src:
url("@/assets/fonts/Urbanist-BoldItalic.woff2") format("woff2"),
url("@/assets/fonts/Urbanist-BoldItalic.woff") format("woff");
font-weight: 700;
font-style: italic;
}

View file

@ -14,17 +14,17 @@ html {
border-radius: 60px; border-radius: 60px;
.button-content { .button-content {
border: none; border: 1px solid $red;
}
&:hover {
@include box-shadow-hover($red-200);
z-index: 5;
} }
input[type="radio"]:focus+.list-button-content { input[type="radio"]:focus+.list-button-content {
box-shadow: 0 0 0 2.5px $red; box-shadow: 0 0 0 2.5px $red;
} }
&:not(.selected):not(.no-hover):hover {
position: relative;
@include box-shadow-hover($red-200);
z-index: 5;
}
} }
&.list-card { &.list-card {

View file

@ -1,7 +1,9 @@
.base-input-button { .base-input-button {
&:not(.has-error):hover { &:not(.disabled) {
cursor: pointer; cursor: pointer;
}
&:not(.has-error):hover {
&.list-button, &.list-button,
&.list-button-horizontal, &.list-button-horizontal,
&.list-card { &.list-card {

View file

@ -1,4 +1,5 @@
$svg-calendar-picker: "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"; $svg-calendar-picker: "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";
$svg-error-calendar-graphic: "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='" + $svg-calendar-error-stroke-color + "'/%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='" + $svg-calendar-error-stroke-color + "'/%3E%3C/svg%3E";
$svg-date-picker-nav-back-button: "data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.331685 6.00121C0.330445 5.82446 0.399256 5.65442 0.523053 5.52832L5.84499 0.198256C5.97188 0.0713149 6.14397 -8.63821e-08 6.32341 -6.72174e-08C6.50285 -4.80527e-08 6.67495 0.071315 6.80183 0.198256C6.92872 0.325198 7 0.497368 7 0.67689C7 0.856412 6.92872 1.02858 6.80183 1.15552L1.94874 6.00121L6.80183 10.8526C6.92745 10.9796 6.99751 11.1512 6.99662 11.3299C6.99572 11.5085 6.92393 11.6794 6.79705 11.8051C6.67016 11.9308 6.49857 12.0009 6.32003 12C6.14148 11.9991 5.97061 11.9273 5.84499 11.8003L0.526881 6.47601C0.401568 6.34983 0.331375 6.17908 0.331685 6.00121Z' fill='%231574A1'/%3E%3C/svg%3E%0A"; $svg-date-picker-nav-back-button: "data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.331685 6.00121C0.330445 5.82446 0.399256 5.65442 0.523053 5.52832L5.84499 0.198256C5.97188 0.0713149 6.14397 -8.63821e-08 6.32341 -6.72174e-08C6.50285 -4.80527e-08 6.67495 0.071315 6.80183 0.198256C6.92872 0.325198 7 0.497368 7 0.67689C7 0.856412 6.92872 1.02858 6.80183 1.15552L1.94874 6.00121L6.80183 10.8526C6.92745 10.9796 6.99751 11.1512 6.99662 11.3299C6.99572 11.5085 6.92393 11.6794 6.79705 11.8051C6.67016 11.9308 6.49857 12.0009 6.32003 12C6.14148 11.9991 5.97061 11.9273 5.84499 11.8003L0.526881 6.47601C0.401568 6.34983 0.331375 6.17908 0.331685 6.00121Z' fill='%231574A1'/%3E%3C/svg%3E%0A";
$svg-date-picker-forward-button: "data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.66831 5.99879C6.66955 6.17554 6.60074 6.34558 6.47695 6.47168L1.15501 11.8017C1.02812 11.9287 0.85603 12 0.676587 12C0.497145 12 0.325053 11.9287 0.198168 11.8017C0.0712831 11.6748 7.268e-09 11.5026 8.07183e-09 11.3231C8.87567e-09 11.1436 0.0712831 10.9714 0.198168 10.8445L5.05126 5.99879L0.198168 1.14736C0.0725521 1.02042 0.00248585 0.848755 0.00338306 0.670131C0.00428028 0.491506 0.0760674 0.320554 0.202952 0.194881C0.329837 0.0692091 0.501426 -0.000888818 0.679971 9.54485e-06C0.858515 0.000906955 1.02939 0.0727263 1.15501 0.199668L6.47312 5.52399C6.59843 5.65017 6.66862 5.82092 6.66831 5.99879Z' fill='%231574A1'/%3E%3C/svg%3E%0A"; $svg-date-picker-forward-button: "data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.66831 5.99879C6.66955 6.17554 6.60074 6.34558 6.47695 6.47168L1.15501 11.8017C1.02812 11.9287 0.85603 12 0.676587 12C0.497145 12 0.325053 11.9287 0.198168 11.8017C0.0712831 11.6748 7.268e-09 11.5026 8.07183e-09 11.3231C8.87567e-09 11.1436 0.0712831 10.9714 0.198168 10.8445L5.05126 5.99879L0.198168 1.14736C0.0725521 1.02042 0.00248585 0.848755 0.00338306 0.670131C0.00428028 0.491506 0.0760674 0.320554 0.202952 0.194881C0.329837 0.0692091 0.501426 -0.000888818 0.679971 9.54485e-06C0.858515 0.000906955 1.02939 0.0727263 1.15501 0.199668L6.47312 5.52399C6.59843 5.65017 6.66862 5.82092 6.66831 5.99879Z' fill='%231574A1'/%3E%3C/svg%3E%0A";
$svg-drop-off-alert: "data:image/svg+xml,%3Csvg viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_13957_112512)'%3E%3Cpath d='M5.99865 0C4.81147 4.82643e-07 3.65095 0.352111 2.66392 1.01179C1.67688 1.67146 0.907678 2.60907 0.45361 3.70599C-0.000459241 4.80291 -0.11899 6.00986 0.113013 7.17415C0.345015 8.33845 0.917126 9.40778 1.75697 10.2469C2.59682 11.086 3.66666 11.6571 4.83117 11.8881C5.99567 12.119 7.20251 11.9994 8.29902 11.5443C9.39553 11.0893 10.3324 10.3192 10.9912 9.33159C11.65 8.34396 12.0011 7.18313 12 5.99594C11.9971 4.40566 11.3638 2.88142 10.2388 1.75742C9.11375 0.633431 7.58894 0.00143011 5.99865 0V0ZM5.99865 11.2478C4.96135 11.2473 3.94748 10.9392 3.08518 10.3627C2.22288 9.7861 1.55085 8.96685 1.15401 8.00846C0.75718 7.05006 0.653353 5.99554 0.855656 4.97815C1.05796 3.96077 1.55731 3.02618 2.29061 2.29251C3.0239 1.55884 3.95823 1.059 4.97551 0.856176C5.99279 0.653349 7.04737 0.756633 8.00597 1.15297C8.96457 1.54931 9.78416 2.22092 10.3612 3.08293C10.9382 3.94493 11.2467 4.95864 11.2478 5.99594C11.2478 7.38835 10.6949 8.72377 9.71053 9.70861C8.7262 10.6934 7.39106 11.2471 5.99865 11.2478V11.2478Z' fill='%2306577C'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.22736 8.84695C6.30613 8.76818 6.35038 8.66135 6.35038 8.54996V5.30996C6.35038 5.19857 6.30613 5.09174 6.22736 5.01298C6.1486 4.93421 6.04177 4.88996 5.93038 4.88996C5.81899 4.88996 5.71216 4.93421 5.63339 5.01298C5.55463 5.09174 5.51038 5.19857 5.51038 5.30996V8.54996C5.51038 8.66135 5.55463 8.76818 5.63339 8.84695C5.71216 8.92571 5.81899 8.96996 5.93038 8.96996C6.04177 8.96996 6.1486 8.92571 6.22736 8.84695ZM5.69704 3.97918C5.76611 4.02533 5.84731 4.04996 5.93038 4.04996C5.98558 4.05012 6.04026 4.03936 6.09129 4.01831C6.14232 3.99726 6.18868 3.96633 6.22771 3.9273C6.26675 3.88827 6.29768 3.8419 6.31873 3.79088C6.33978 3.73985 6.35053 3.68516 6.35038 3.62996C6.35038 3.54689 6.32574 3.46569 6.27959 3.39662C6.23344 3.32755 6.16785 3.27372 6.0911 3.24193C6.01436 3.21014 5.92991 3.20183 5.84844 3.21803C5.76697 3.23424 5.69213 3.27424 5.63339 3.33298C5.57465 3.39171 5.53465 3.46655 5.51845 3.54802C5.50224 3.6295 5.51056 3.71394 5.54235 3.79069C5.57414 3.86743 5.62797 3.93303 5.69704 3.97918Z' fill='%2306577C'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_13957_112512'%3E%3Crect width='12' height='12' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A"; $svg-drop-off-alert: "data:image/svg+xml,%3Csvg viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_13957_112512)'%3E%3Cpath d='M5.99865 0C4.81147 4.82643e-07 3.65095 0.352111 2.66392 1.01179C1.67688 1.67146 0.907678 2.60907 0.45361 3.70599C-0.000459241 4.80291 -0.11899 6.00986 0.113013 7.17415C0.345015 8.33845 0.917126 9.40778 1.75697 10.2469C2.59682 11.086 3.66666 11.6571 4.83117 11.8881C5.99567 12.119 7.20251 11.9994 8.29902 11.5443C9.39553 11.0893 10.3324 10.3192 10.9912 9.33159C11.65 8.34396 12.0011 7.18313 12 5.99594C11.9971 4.40566 11.3638 2.88142 10.2388 1.75742C9.11375 0.633431 7.58894 0.00143011 5.99865 0V0ZM5.99865 11.2478C4.96135 11.2473 3.94748 10.9392 3.08518 10.3627C2.22288 9.7861 1.55085 8.96685 1.15401 8.00846C0.75718 7.05006 0.653353 5.99554 0.855656 4.97815C1.05796 3.96077 1.55731 3.02618 2.29061 2.29251C3.0239 1.55884 3.95823 1.059 4.97551 0.856176C5.99279 0.653349 7.04737 0.756633 8.00597 1.15297C8.96457 1.54931 9.78416 2.22092 10.3612 3.08293C10.9382 3.94493 11.2467 4.95864 11.2478 5.99594C11.2478 7.38835 10.6949 8.72377 9.71053 9.70861C8.7262 10.6934 7.39106 11.2471 5.99865 11.2478V11.2478Z' fill='%2306577C'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.22736 8.84695C6.30613 8.76818 6.35038 8.66135 6.35038 8.54996V5.30996C6.35038 5.19857 6.30613 5.09174 6.22736 5.01298C6.1486 4.93421 6.04177 4.88996 5.93038 4.88996C5.81899 4.88996 5.71216 4.93421 5.63339 5.01298C5.55463 5.09174 5.51038 5.19857 5.51038 5.30996V8.54996C5.51038 8.66135 5.55463 8.76818 5.63339 8.84695C5.71216 8.92571 5.81899 8.96996 5.93038 8.96996C6.04177 8.96996 6.1486 8.92571 6.22736 8.84695ZM5.69704 3.97918C5.76611 4.02533 5.84731 4.04996 5.93038 4.04996C5.98558 4.05012 6.04026 4.03936 6.09129 4.01831C6.14232 3.99726 6.18868 3.96633 6.22771 3.9273C6.26675 3.88827 6.29768 3.8419 6.31873 3.79088C6.33978 3.73985 6.35053 3.68516 6.35038 3.62996C6.35038 3.54689 6.32574 3.46569 6.27959 3.39662C6.23344 3.32755 6.16785 3.27372 6.0911 3.24193C6.01436 3.21014 5.92991 3.20183 5.84844 3.21803C5.76697 3.23424 5.69213 3.27424 5.63339 3.33298C5.57465 3.39171 5.53465 3.46655 5.51845 3.54802C5.50224 3.6295 5.51056 3.71394 5.54235 3.79069C5.57414 3.86743 5.62797 3.93303 5.69704 3.97918Z' fill='%2306577C'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_13957_112512'%3E%3Crect width='12' height='12' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A";

View file

@ -70,8 +70,13 @@ $orange: #fd7e14;
$teal: #20c997; $teal: #20c997;
$cyan: #0dcaf0; $cyan: #0dcaf0;
//Heritage
$heritage-blue-primary: #0070D1;
$heritage-blue-secondary: #167cac;
// Darker gray // Darker gray
$darker-gray: #525656; $darker-gray: #525656;
$lighter-gray: #9aa1a3;
// scss-docs-start colors-map // scss-docs-start colors-map
$colors: ( $colors: (
@ -114,7 +119,7 @@ $theme-colors: (
); );
//Default font color //Default font color
$body-color: $gray-600; $body-color: $darker-gray;
//Fonts //Fonts
$font-family-sans-serif: Urbanist, Roboto, Arial, sans-serif; $font-family-sans-serif: Urbanist, Roboto, Arial, sans-serif;
@ -153,6 +158,7 @@ $font-weight-bolder: bolder;
$border-radius: 0.25rem; $border-radius: 0.25rem;
$border-radius-sm: 0.2rem; $border-radius-sm: 0.2rem;
$border-radius-lg: 0.5rem; //Used for buttons. Can be used for other things, of course. $border-radius-lg: 0.5rem; //Used for buttons. Can be used for other things, of course.
$border-radius-xl: 1.375rem;
$border-radius-pill: 50rem; $border-radius-pill: 50rem;
$border-radius-list-button: 1.375rem; // Used for list buttons $border-radius-list-button: 1.375rem; // Used for list buttons
@ -205,6 +211,11 @@ $alert-bg-scale: -90%;
$alert-border-scale: -100%; $alert-border-scale: -100%;
$alert-color-scale: 40%; $alert-color-scale: 40%;
$alert-yellow-bg: #fef6e8;
$alert-yellow-color: #e86421;
$alert-red-bg: #fbe9e8;
$alert-red-color: $red;
//Modal animation //Modal animation
// This affects all [Bootstrap] modals // This affects all [Bootstrap] modals
$modal-fade-transform: translate(0, 100%); $modal-fade-transform: translate(0, 100%);
@ -216,9 +227,8 @@ $enable-important-utilities: false;
// Letter Spacing // Letter Spacing
$letter-spacing-primary: 0.03em; $letter-spacing-primary: 0.03em;
// Borders // Borders
$border-input: 1px solid rgba(179, 180, 181); $border-input: 1px solid $gray-350;
$border-input-focus: 2.5px solid $blue; $border-input-focus: 2.5px solid $heritage-blue-primary;
//Heritage $svg-calendar-error-fill-color: $alert-red-bg;
$heritage-blue-primary: #0070D1; $svg-calendar-error-stroke-color: '%23db0020'; // URL encoded #db0020
$heritage-blue-secondary: #167cac;

View file

@ -1,64 +1,85 @@
<template> <template>
<div <div
class="alert fade show text-center mb-0 py-2 px-4" class="alert fade show"
role="alert" role="alert"
:class="[ :class="[
isDismissible ? 'alert-dismissible' : '', isDismissible ? 'alert-dismissible' : '',
alertClass, alertClass,
cssClassNameForCmsWidget, cssClassNameForCmsWidget,
]"> ]">
<p class="m-0 fw-bold alert-heading"> <div class="alert-header">
{{ alertHeadline }} <svg class="alert-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="21" viewBox="0 0 20 21">
</p> <path fill-rule="nonzero" d="M10 .5c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10S4.477.5 10 .5zm.043 13.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm.77-9.2h-1.54l-.088.009a.502.502 0 0 0-.299.193.66.66 0 0 0-.125.47l.747 6.806.017.096c.065.249.263.425.495.426l.084-.008c.22-.042.396-.246.427-.51l.793-6.805.004-.102a.651.651 0 0 0-.127-.37.489.489 0 0 0-.388-.205z"/>
<template
v-for="paragraph in splitAlertCopyForParagraphTag"
:key="paragraph">
<p
v-if="!doesCopyContainRouterLink(paragraph) && !doesCopyContainTextLink(paragraph)"
class="m-0 text-body small"
v-html="paragraph"></p>
<p
v-else
class="m-0 text-body small">
<template v-if="doesCopyContainRouterLink(paragraph)">
<textBlock
:customText="paragraph"
class="mb-1"
marginTopSizeOverride="0" />
</template>
<template
v-for="copy in splitCopyOnCMSPlaceHolder(paragraph)"
v-else
:key="copy">
<span v-if="doesCopyContainTextLink(copy)">
<textLink
linkType="text"
:text="getRouterLinkDisplayTextFromCopy(copy)"
href="#!"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
@click-event="
$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))
" />
</span>
<span
v-else
v-html="copy"></span>
</template>
</p>
</template>
<button
type="button"
class="btn-close p-2"
data-bs-dismiss="alert"
aria-label="Close">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 23.7 23.7"
xml:space="preserve">
<path
d="m23.24 2.7-9.15 9.15L23.24 21a1.581 1.581 0 0 1-1.12 2.7c-.42 0-.82-.16-1.12-.46l-9.15-9.15-9.15 9.15c-.3.3-.7.46-1.12.46A1.581 1.581 0 0 1 .46 21l8.47-8.47.68-.68L.46 2.7c-.62-.62-.62-1.62 0-2.24.62-.62 1.62-.62 2.24 0l8.47 8.47.68.68L21 .46a1.57 1.57 0 0 1 2.23 0c.63.62.63 1.62.01 2.24z" />
</svg> </svg>
</button> <span class="m-0 fw-bold alert-header-text">
{{ alertHeadline }}
</span>
<button
v-if="isCollapsible"
class="btn-collapse"
type="button"
@click="toggleCollapse">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="9" viewBox="0 0 15 9"
class="btn-collapse-icon"
:class="{ 'rotated': !collapsed }">
<path fill-rule="nonzero" d="M7.5 0a.806.806 0 0 0-.593.265L.246 7.455a.957.957 0 0 0 0 1.28.796.796 0 0 0 1.185 0l6.07-6.55 6.068 6.55a.796.796 0 0 0 1.186 0 .957.957 0 0 0 0-1.28L8.093.265A.806.806 0 0 0 7.5 0"/>
</svg>
</button>
<button
v-if="isDismissible"
type="button"
class="btn-close p-2"
data-bs-dismiss="alert"
aria-label="Close">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 23.7 23.7"
xml:space="preserve">
<path
d="m23.24 2.7-9.15 9.15L23.24 21a1.581 1.581 0 0 1-1.12 2.7c-.42 0-.82-.16-1.12-.46l-9.15-9.15-9.15 9.15c-.3.3-.7.46-1.12.46A1.581 1.581 0 0 1 .46 21l8.47-8.47.68-.68L.46 2.7c-.62-.62-.62-1.62 0-2.24.62-.62 1.62-.62 2.24 0l8.47 8.47.68.68L21 .46a1.57 1.57 0 0 1 2.23 0c.63.62.63 1.62.01 2.24z" />
</svg>
</button>
</div>
<div class="alert-body"
v-show="alertCopy"
:id="collapseId">
<template
v-for="paragraph in splitAlertCopyForParagraphTag"
:key="paragraph">
<p
v-if="!doesCopyContainRouterLink(paragraph) && !doesCopyContainTextLink(paragraph)"
class="m-0 text-body"
v-html="paragraph"></p>
<p
v-else
class="m-0 text-body">
<template v-if="doesCopyContainRouterLink(paragraph)">
<textBlock
:customText="paragraph"
class="mb-1"
marginTopSizeOverride="0" />
</template>
<template
v-for="copy in splitCopyOnCMSPlaceHolder(paragraph)"
v-else
:key="copy">
<span v-if="doesCopyContainTextLink(copy)">
<textLink
linkType="text"
:text="getRouterLinkDisplayTextFromCopy(copy)"
href="#!"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
@click-event="
$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))
" />
</span>
<span
v-else
v-html="copy"></span>
</template>
</p>
</template>
</div>
</div> </div>
</template> </template>
@ -74,6 +95,7 @@ import {
import applicationConfig from '@/constants/application-config'; import applicationConfig from '@/constants/application-config';
import textBlock from '@/digital-components/text-block/text-block.vue'; import textBlock from '@/digital-components/text-block/text-block.vue';
import textLink from '@/ux-components/text-link/text-link.vue'; import textLink from '@/ux-components/text-link/text-link.vue';
import { Collapse } from 'bootstrap';
export default { export default {
// eslint-disable-next-line vue/multi-word-component-names // eslint-disable-next-line vue/multi-word-component-names
@ -84,13 +106,14 @@ export default {
}, },
props: { props: {
isDismissible: Boolean, isDismissible: Boolean,
isCollapsible: Boolean,
/* /*
alertClass class names: alertClass class names:
alert-success (green) alert-success (green)
alert-danger (red) alert-danger (red)
alert-warning (yellow) alert-warning (yellow)
alert-info (blue) alert-info (blue)
*/ */
alertClass: String, alertClass: String,
cmsWidgetName: { cmsWidgetName: {
type: String, type: String,
@ -106,7 +129,14 @@ export default {
shouldScrollToOnMount: { shouldScrollToOnMount: {
type: Boolean, type: Boolean,
default: true default: true
} },
startCollapsed: Boolean
},
data() {
return {
collapsed: this.startCollapsed || false,
collapseElement: null,
};
}, },
computed: { computed: {
pageQueryString() { pageQueryString() {
@ -124,10 +154,18 @@ export default {
}, },
splitAlertCopyForParagraphTag() { splitAlertCopyForParagraphTag() {
return splitCMSCopyOnParagraphTag(this.alertCopy); return splitCMSCopyOnParagraphTag(this.alertCopy);
} },
collapseId() {
return this.isCollapsible
? `${this.cmsWidgetName}-collapse`
: null;
},
}, },
mounted() { mounted() {
this.ensureAlertIsInViewPort(); this.ensureAlertIsInViewPort();
if (this.isCollapsible) {
this.collapseElement = Collapse.getOrCreateInstance(document.getElementById(this.collapseId));
}
}, },
methods: { methods: {
doesCopyContainRouterLink, doesCopyContainRouterLink,
@ -152,37 +190,83 @@ export default {
<= (window.innerHeight - footerHeight <= (window.innerHeight - footerHeight
|| document.documentElement.clientHeight - footerHeight) || document.documentElement.clientHeight - footerHeight)
); );
} },
toggleCollapse() {
this.collapsed = !this.collapsed;
if (this.collapsed) {
this.collapseElement.hide();
} else {
this.collapseElement.show();
}
},
} }
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.alert { .alert {
border: 1px solid;
padding: 0rem;
margin-bottom: 2rem;
.alert-header {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
padding: .75rem 1rem .75rem 1rem;
font-weight: 600;
color: $black;
}
.alert-icon {
min-width: 1rem;
max-height: 1rem;
}
.alert-header-text {
flex-grow: 1;
padding: 0rem .625rem;
}
.btn-collapse {
display: flex;
border: none;
background: none;
min-width: 1rem;
max-height: 1rem;
padding: 0;
}
.btn-collapse-icon {
height: 100%;
width: 100%;
transition: transform 0.3s;
transform: rotate(180deg);
&.rotated {
transform: none;
}
}
a { a {
text-decoration: none; font-weight: $font-weight-normal;
} }
border-color: transparent; border-color: transparent;
button {
display: none;
}
.btn-close { .btn-close {
background: none; background: none;
opacity: 1; opacity: 1;
width: 0.75rem; width: 0.75rem;
height: 0.75rem; height: 0.75rem;
top: 2px;
right: 2px;
} }
&.alert-dismissible { .alert-body {
button { margin: 0 1rem;
display: flex; padding: .5rem 1rem .75rem 1rem;
top: 2px; border-top: 1px solid;
right: 2px; color: $darker-gray;
} line-height: 26px;
font-weight: 400;
} }
&.alert-info { &.alert-info {
background-color: $blue-100; background-color: $blue-100;
.alert-heading { border-color: $blue-200;
color: $blue-700; .alert-body {
border-top-color: $blue-200;
} }
svg { svg {
fill: $blue-700; fill: $blue-700;
@ -191,9 +275,10 @@ export default {
} }
} }
&.alert-danger { &.alert-danger {
background-color: $red-100; background-color: $alert-red-bg;
.alert-heading { border-color: $alert-red-color;
color: $red-600; .alert-body {
border-top-color: $alert-red-color;
} }
svg { svg {
fill: $red-600; fill: $red-600;
@ -202,20 +287,20 @@ export default {
} }
} }
&.alert-warning { &.alert-warning {
background-color: $yellow-100; background-color: $alert-yellow-bg;
.alert-heading { border-color: $alert-yellow-color;
color: $yellow-600; .alert-body {
border-top-color: $alert-yellow-color;
} }
svg { svg {
fill: $yellow-600; fill: $alert-yellow-color;
width: 1rem;
height: 1rem;
} }
} }
&.alert-success { &.alert-success {
background-color: $green-100; background-color: $green-100;
.alert-heading { border-color: $green-200;
color: $green-700; .alert-body {
border-top-color: $green-200;
} }
svg { svg {
fill: $green-700; fill: $green-700;
@ -223,9 +308,5 @@ export default {
height: 1rem; height: 1rem;
} }
} }
& p {
font-size: 0.875rem;
margin-bottom: 0.25rem !important;
}
} }
</style> </style>

View file

@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { getMountOptions } from '@/helpers/unit-test-helper.js';
import buttonMain from '@/ux-components/button-main/button-main.vue'; import buttonMain from '@/ux-components/button-main/button-main.vue';
import buttonVariants from '@/constants/button-variants'; import { buttonVariants } from '@/constants/component-variants';
import buttonSizes from '@/constants/button-sizes'; import buttonSizes from '@/constants/button-sizes';
/** @ignore */ /** @ignore */

View file

@ -9,7 +9,7 @@
</template> </template>
<script> <script>
import buttonVariants from '@/constants/button-variants'; import { buttonVariants } from '@/constants/component-variants';
import buttonSizes from '@/constants/button-sizes'; import buttonSizes from '@/constants/button-sizes';
export default { export default {

View file

@ -5,7 +5,10 @@
buttonWrapperClasses="list-group base-input-button list-button no-hover d-flex flex-column w-100 mb-2"> buttonWrapperClasses="list-group base-input-button list-button no-hover d-flex flex-column w-100 mb-2">
<div <div
:aria-label="buttonLabel" :aria-label="buttonLabel"
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4"> class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4"
:class="{
'has-sub-copy': buttonLabelSubCopy,
}">
<span <span
class="m-0" class="m-0"
:class="textPosition"> :class="textPosition">
@ -85,6 +88,10 @@ $heritage-checked-border-color: #0070d1;
background: $heritage-checked-background-color; background: $heritage-checked-background-color;
border-color: $heritage-checked-border-color; border-color: $heritage-checked-border-color;
box-shadow: 0 0 0 1px $blue; box-shadow: 0 0 0 1px $blue;
&:not(.has-sub-copy) {
font-weight: 500;
color: $black;
}
} }
&:checked + .list-button-content span:nth-child(2) { &:checked + .list-button-content span:nth-child(2) {
font-weight: 400; font-weight: 400;
@ -104,10 +111,15 @@ $heritage-checked-border-color: #0070d1;
width: 100%; width: 100%;
outline: none; outline: none;
&:not(.has-sub-copy) {
font-weight: 400;
color: $darker-gray;
}
span { span {
&.small { &.small {
color: $darker-gray; color: $darker-gray;
} }
} }
} }
</style> </style>

View file

@ -62,7 +62,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
a { a {
color: #0070d1; color: $heritage-blue-primary;
text-decoration: none; text-decoration: none;
line-height: 1.56; line-height: 1.56;
padding: 0; padding: 0;