Merge remote-tracking branch 'origin/develop' into feature/kiener/INSR-6047
This commit is contained in:
commit
204fc0a9e2
82 changed files with 7631 additions and 3376 deletions
|
|
@ -8,9 +8,18 @@ schedules:
|
|||
- develop
|
||||
pool: 'Default'
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
- repository: AzureDevOps
|
||||
type: github
|
||||
name: Safelite/AzureDevOps
|
||||
endpoint: Safelite
|
||||
ref: refs/tags/t5.7.40
|
||||
|
||||
variables:
|
||||
# - group: Digital-Infrastructure
|
||||
# - group: ISS-BuildBranches
|
||||
- group: SafelitePlaywright
|
||||
- name: dockerImageName
|
||||
value: 'playwright-tests'
|
||||
- name: imageTag
|
||||
|
|
@ -30,158 +39,17 @@ stages:
|
|||
- stage: TestPr
|
||||
displayName: Run Playwright Test
|
||||
jobs:
|
||||
- job: playwright_tests
|
||||
continueOnError: true
|
||||
strategy:
|
||||
matrix:
|
||||
shard1:
|
||||
shardNumber: 1
|
||||
shard2:
|
||||
shardNumber: 2
|
||||
shard3:
|
||||
shardNumber: 3
|
||||
shard4:
|
||||
shardNumber: 4
|
||||
|
||||
steps:
|
||||
- task: Docker@2
|
||||
displayName: 'Build Docker Image'
|
||||
inputs:
|
||||
command: build
|
||||
dockerfile: Dockerfile.playwright
|
||||
repository: $(dockerImageName)
|
||||
tags: $(imageTag)
|
||||
arguments: '--no-cache --pull'
|
||||
|
||||
- script: |
|
||||
# Create container and run tests
|
||||
container_id=$(docker create \
|
||||
--ipc=host \
|
||||
-e CCIS_API_AUTH=$(CCIS_API_AUTH) \
|
||||
-e BASE_URL=$(BASE_URL) \
|
||||
-e CCIS_API_URL=$(CCIS_API_URL) \
|
||||
-e ADMIN_SERVICE_API_URL=$(ADMIN_SERVICE_API_URL) \
|
||||
-e SHARD=$(shardNumber) \
|
||||
-e CI=true \
|
||||
-e NODE_ENV=$(NODE_ENV) \
|
||||
$(dockerImageName):$(imageTag) \
|
||||
npx concurrently -k -n "server,playwright"\
|
||||
"sed -i \"s|^process\.env\.VUE_APP_CONSUMER_CF_DISTRO = .*|process\.env\.VUE_APP_CONSUMER_CF_DISTRO='https://digitalapi.test.safelite.io'|\" \"./vue.config.js\" && echo \"Updated config file to use TEST APIs\" && npm run serve -- --port=8080"\
|
||||
"npx wait-on http://localhost:8080 && npm run test:playwright -- --shard=$(shardNumber)/$(totalShards) --reporter=list,blob --grep \"@smoke|@Advanced\"")
|
||||
|
||||
# Start container and stream logs
|
||||
echo "Starting tests for shard $(shardNumber)..."
|
||||
docker start -a $container_id
|
||||
|
||||
# Create directory for test results
|
||||
echo "Creating test results directory..."
|
||||
mkdir -p $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)
|
||||
|
||||
# Copy test results from container
|
||||
echo "Copying test results..."
|
||||
docker cp $container_id:/app/blob-report/. $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)/
|
||||
|
||||
# Remove container
|
||||
echo "Cleaning up container..."
|
||||
docker rm $container_id
|
||||
|
||||
# Check if tests failed
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Tests failed in shard $(shardNumber) or tests don't exist for this shard number!"
|
||||
exit 0 # Suppress error. It will be visible in report.
|
||||
fi
|
||||
displayName: 'Run Playwright Tests - Shard $(shardNumber)'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish Test Reports - Shard $(shardNumber)'
|
||||
condition: always()
|
||||
inputs:
|
||||
targetPath: '$(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)'
|
||||
artifact: 'playwright-report-shard-$(shardNumber)'
|
||||
publishLocation: 'pipeline'
|
||||
|
||||
- script: |
|
||||
docker rmi $(dockerImageName):$(imageTag) -f
|
||||
displayName: 'Cleanup Docker Image'
|
||||
condition: always()
|
||||
|
||||
- job: download_and_merge_reports
|
||||
dependsOn: playwright_tests
|
||||
timeoutInMinutes: 8
|
||||
cancelTimeoutInMinutes: 10
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
targetPath: '$(System.DefaultWorkingDirectory)/playwright-reports'
|
||||
- task: Docker@2
|
||||
displayName: 'Build Docker Image'
|
||||
inputs:
|
||||
command: build
|
||||
dockerfile: Dockerfile.playwright
|
||||
repository: $(dockerImageName)
|
||||
tags: $(imageTag)
|
||||
arguments: '--no-cache --pull'
|
||||
- bash: |
|
||||
# Create container for jira writeback
|
||||
container_id=$(docker create \
|
||||
--ipc=host \
|
||||
-e JIRA_SERVER=$(JIRA_SERVER) \
|
||||
-e JIRA_USERNAME=$(JIRA_USERNAME) \
|
||||
-e JIRA_API_KEY=$(JIRA_API_KEY) \
|
||||
-e IS_REGRESSION=$(IS_REGRESSION) \
|
||||
-e JIRA_BOARD_ID=$(JIRA_BOARD_ID) \
|
||||
-e JIRA_EPIC_KEY=$(JIRA_EPIC_KEY) \
|
||||
-e JIRA_PROJECT_KEY=$(JIRA_PROJECT_KEY) \
|
||||
$(dockerImageName):$(imageTag) \
|
||||
bash -c "echo \"Moving Playwright reports out of subfolders...\" &&
|
||||
find ./playwright-reports/ -mindepth 2 -type f -exec mv {} ./playwright-reports/ \; &&
|
||||
echo \"Merging reports...\" &&
|
||||
PLAYWRIGHT_JUNIT_OUTPUT_DIR='/app/test-results' PLAYWRIGHT_JUNIT_OUTPUT_NAME='junit_results.xml' npx playwright merge-reports --reporter='playwright-tests/impl/reporter/JiraWritebackReporter.ts',junit ./playwright-reports
|
||||
echo \"Contents of ortoni-report:\" && ls ./ortoni-report &&
|
||||
echo 'Current dir: ' && pwd
|
||||
echo 'Contents of current dir: ' && ls
|
||||
echo 'Contents of /app/test-results' && ls /app/test-results ")
|
||||
|
||||
# Start container and stream logs
|
||||
echo "Starting merge"
|
||||
docker start -a $container_id
|
||||
|
||||
# Create directory for test results
|
||||
echo "Creating test results directory..."
|
||||
mkdir -p $(System.DefaultWorkingDirectory)/ortoni-report
|
||||
mkdir -p $(System.DefaultWorkingDirectory)/test-results
|
||||
|
||||
# Copy test results from container
|
||||
echo "Copying test results..."
|
||||
docker cp $container_id:/app/ortoni-report/. $(System.DefaultWorkingDirectory)/ortoni-report
|
||||
docker cp $container_id:/app/test-results/junit_results.xml $(System.DefaultWorkingDirectory)/test-results
|
||||
|
||||
# Remove container
|
||||
echo "Cleaning up container..."
|
||||
docker rm $container_id
|
||||
env:
|
||||
JIRA_API_KEY: $(JIRA_API_KEY)
|
||||
displayName: merge_and_publish_results_to_jira
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: 'Publish test results'
|
||||
inputs:
|
||||
searchFolder: 'test-results'
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: 'junit_results.xml'
|
||||
mergeTestResults: true
|
||||
failTaskOnFailedTests: false
|
||||
testRunTitle: 'Playwright Tests'
|
||||
condition: succeededOrFailed()
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish Merged Report'
|
||||
condition: always()
|
||||
inputs:
|
||||
targetPath: '$(System.DefaultWorkingDirectory)/ortoni-report'
|
||||
artifact: 'playwright-merged-report'
|
||||
publishLocation: 'pipeline'
|
||||
- script: |
|
||||
docker rmi $(dockerImageName):$(imageTag) -f
|
||||
displayName: 'Cleanup Docker Image'
|
||||
condition: always()
|
||||
- template: templates/digital/playwright-test.yml@AzureDevOps
|
||||
parameters:
|
||||
applicationType: 'vue'
|
||||
totalShards: ${{ variables.totalShards }}
|
||||
targetUrl: $(BASE_URL)
|
||||
dockerFileName: 'Dockerfile.playwright'
|
||||
isRegression: ${{ variables.IS_REGRESSION }}
|
||||
filterTags: '@Advanced'
|
||||
playwrightTestsPath: 'playwright-tests'
|
||||
npmServePath: '.'
|
||||
npmrcPath: 'playwright-tests/.npmrc'
|
||||
secrets:
|
||||
CCIS_API_AUTH: $(CCIS_API_AUTH)
|
||||
JIRA_API_KEY: $(JIRA_API_KEY)
|
||||
199
package-lock.json
generated
199
package-lock.json
generated
|
|
@ -142,7 +142,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz",
|
||||
"integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
|
|
@ -2374,7 +2373,8 @@
|
|||
"integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
|
|
@ -3281,6 +3281,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@gar/promisify": "^1.0.1",
|
||||
"semver": "^7.3.5"
|
||||
|
|
@ -3293,6 +3294,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
|
@ -3308,6 +3310,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"rimraf": "^3.0.2"
|
||||
|
|
@ -3323,6 +3326,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
|
|
@ -3393,7 +3397,6 @@
|
|||
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"playwright": "1.56.1"
|
||||
},
|
||||
|
|
@ -3973,7 +3976,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.1.tgz",
|
||||
"integrity": "sha512-P6iIPyYQ+qH8CvGauAqanhVnjrnRe0IZFSYCeGkSRW9q3u8bdVn2NPI+lasFyVsEQn1J/IFmp5Aax41+dAP9wg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
|
|
@ -4440,7 +4442,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
|
||||
"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "*",
|
||||
"@types/mdurl": "*"
|
||||
|
|
@ -4625,7 +4626,6 @@
|
|||
"integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "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",
|
||||
"integrity": "sha512-nV7tYQLe7YsTtzFrfOMIHc5N2hp5lHG2rpYr0aNja9rNljdgcPZLyQRb2YRivTHqTv7lI962UXFURcpStHgyFw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-compilation-targets": "^7.12.16",
|
||||
"@soda/friendly-errors-webpack-plugin": "^1.8.0",
|
||||
|
|
@ -5640,7 +5639,6 @@
|
|||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
|
||||
"integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.20.15",
|
||||
"@vue/compiler-core": "3.3.4",
|
||||
|
|
@ -5775,7 +5773,6 @@
|
|||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
|
||||
"integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "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",
|
||||
"integrity": "sha512-VL61CgZBoQqayXfzlZJHHpZuX4lsT8dmdZMJzADhdAJjKu26JBpypHr/2ppevxItljPiuALQW4MKhhCXZRXnLg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.2.0",
|
||||
"chalk": "^2.1.0",
|
||||
|
|
@ -6097,7 +6093,6 @@
|
|||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
|
||||
"integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -6201,6 +6196,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"humanize-ms": "^1.2.1"
|
||||
},
|
||||
|
|
@ -6215,6 +6211,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"clean-stack": "^2.0.0",
|
||||
"indent-string": "^4.0.0"
|
||||
|
|
@ -6228,7 +6225,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
|
|
@ -6361,7 +6357,8 @@
|
|||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/arch": {
|
||||
"version": "2.2.0",
|
||||
|
|
@ -6400,6 +6397,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
|
|
@ -6599,7 +6597,6 @@
|
|||
"integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
|
|
@ -6663,7 +6660,6 @@
|
|||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
|
||||
"integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/transform": "^27.5.1",
|
||||
"@jest/types": "^27.5.1",
|
||||
|
|
@ -6984,6 +6980,7 @@
|
|||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
|
|
@ -7128,7 +7125,6 @@
|
|||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"electron-to-chromium": "^1.5.4",
|
||||
|
|
@ -7217,6 +7213,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@npmcli/fs": "^1.0.0",
|
||||
"@npmcli/move-file": "^1.0.1",
|
||||
|
|
@ -7248,6 +7245,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
|
|
@ -7490,6 +7488,7 @@
|
|||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
|
|
@ -7534,6 +7533,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
|
@ -7748,6 +7748,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
|
|
@ -8090,7 +8091,8 @@
|
|||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/consolidate": {
|
||||
"version": "0.15.1",
|
||||
|
|
@ -8292,7 +8294,6 @@
|
|||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
|
||||
"integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"icss-utils": "^5.1.0",
|
||||
"postcss": "^8.4.7",
|
||||
|
|
@ -8369,7 +8370,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
|
||||
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
|
|
@ -8642,6 +8642,7 @@
|
|||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
|
|
@ -8706,6 +8707,7 @@
|
|||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
|
|
@ -8922,7 +8924,8 @@
|
|||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
|
|
@ -8949,6 +8952,7 @@
|
|||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -8969,10 +8973,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
|
|
@ -9125,7 +9130,6 @@
|
|||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
|
||||
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
|
|
@ -9297,6 +9301,33 @@
|
|||
"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": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
|
|
@ -9335,6 +9366,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
|
@ -9345,7 +9377,8 @@
|
|||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"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.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@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",
|
||||
"integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"array-includes": "^3.1.4",
|
||||
"array.prototype.flat": "^1.2.5",
|
||||
|
|
@ -10363,6 +10394,7 @@
|
|||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
|
@ -10621,7 +10653,8 @@
|
|||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
|
|
@ -10886,7 +10919,8 @@
|
|||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "9.1.0",
|
||||
|
|
@ -10909,6 +10943,7 @@
|
|||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
|
|
@ -10986,6 +11021,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3 || ^2.0.0",
|
||||
"color-support": "^1.1.3",
|
||||
|
|
@ -11099,7 +11135,8 @@
|
|||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
|
|
@ -11291,7 +11328,8 @@
|
|||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/hash-sum": {
|
||||
"version": "2.0.0",
|
||||
|
|
@ -11476,7 +11514,8 @@
|
|||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/http-deceiver": {
|
||||
"version": "1.2.7",
|
||||
|
|
@ -11587,6 +11626,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.0.0"
|
||||
}
|
||||
|
|
@ -11731,7 +11771,8 @@
|
|||
"integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
|
|
@ -11776,6 +11817,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"jsbn": "1.1.0",
|
||||
"sprintf-js": "^1.1.3"
|
||||
|
|
@ -11790,7 +11832,8 @@
|
|||
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "2.0.1",
|
||||
|
|
@ -12044,7 +12087,8 @@
|
|||
"integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/is-map": {
|
||||
"version": "2.0.2",
|
||||
|
|
@ -12473,7 +12517,6 @@
|
|||
"resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz",
|
||||
"integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "^27.5.1",
|
||||
"import-local": "^3.0.2",
|
||||
|
|
@ -15187,7 +15230,8 @@
|
|||
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/jsdoc": {
|
||||
"version": "4.0.2",
|
||||
|
|
@ -15513,10 +15557,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
|
|
@ -15860,6 +15905,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"agentkeepalive": "^4.1.3",
|
||||
"cacache": "^15.2.0",
|
||||
|
|
@ -15889,6 +15935,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
|
|
@ -15900,6 +15947,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tootallnate/once": "1",
|
||||
"agent-base": "6",
|
||||
|
|
@ -16122,6 +16170,7 @@
|
|||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
|
|
@ -16162,7 +16211,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
|
||||
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
|
|
@ -16258,6 +16306,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
|
|
@ -16272,6 +16321,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.1.0",
|
||||
"minipass-sized": "^1.0.3",
|
||||
|
|
@ -16291,6 +16341,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
|
|
@ -16305,6 +16356,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
|
|
@ -16319,6 +16371,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
|
|
@ -16332,6 +16385,7 @@
|
|||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
|
|
@ -16357,7 +16411,8 @@
|
|||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/module-alias": {
|
||||
"version": "2.2.2",
|
||||
|
|
@ -16428,7 +16483,8 @@
|
|||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
"version": "1.4.0",
|
||||
|
|
@ -16473,6 +16529,7 @@
|
|||
"integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
|
|
@ -16486,6 +16543,7 @@
|
|||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
|
@ -16498,7 +16556,8 @@
|
|||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
|
|
@ -16559,6 +16618,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"env-paths": "^2.2.0",
|
||||
"glob": "^7.1.4",
|
||||
|
|
@ -16585,6 +16645,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
|
|
@ -16602,6 +16663,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
|
@ -16616,6 +16678,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
|
|
@ -16724,6 +16787,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"are-we-there-yet": "^3.0.0",
|
||||
"console-control-strings": "^1.1.0",
|
||||
|
|
@ -17077,6 +17141,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"aggregate-error": "^3.0.0"
|
||||
},
|
||||
|
|
@ -17313,7 +17378,6 @@
|
|||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.4.tgz",
|
||||
"integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.5.0",
|
||||
"vue-demi": ">=0.14.5"
|
||||
|
|
@ -17468,7 +17532,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
|
|
@ -18001,6 +18064,7 @@
|
|||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
|
|
@ -18116,7 +18180,8 @@
|
|||
"integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/promise-retry": {
|
||||
"version": "2.0.1",
|
||||
|
|
@ -18125,6 +18190,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"err-code": "^2.0.2",
|
||||
"retry": "^0.12.0"
|
||||
|
|
@ -18140,6 +18206,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
|
|
@ -18319,6 +18386,7 @@
|
|||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"dev": true,
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
|
|
@ -18335,6 +18403,7 @@
|
|||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -18783,7 +18852,6 @@
|
|||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz",
|
||||
"integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
|
|
@ -19064,7 +19132,8 @@
|
|||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
|
|
@ -19252,7 +19321,8 @@
|
|||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
|
|
@ -19274,6 +19344,7 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
|
|
@ -19316,6 +19387,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
|
|
@ -19348,6 +19420,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ip-address": "^9.0.5",
|
||||
"smart-buffer": "^4.2.0"
|
||||
|
|
@ -19364,6 +19437,7 @@
|
|||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"agent-base": "^6.0.2",
|
||||
"debug": "^4.3.3",
|
||||
|
|
@ -19912,6 +19986,7 @@
|
|||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
|
|
@ -19930,6 +20005,7 @@
|
|||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
|
|
@ -19942,7 +20018,8 @@
|
|||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tar-fs/node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
|
|
@ -19950,6 +20027,7 @@
|
|||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
|
|
@ -19979,6 +20057,7 @@
|
|||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -19989,6 +20068,7 @@
|
|||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
|
|
@ -20225,7 +20305,6 @@
|
|||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -20503,6 +20582,7 @@
|
|||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
|
|
@ -20558,7 +20638,6 @@
|
|||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -20698,6 +20777,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"unique-slug": "^2.0.0"
|
||||
}
|
||||
|
|
@ -20709,6 +20789,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"imurmurhash": "^0.1.4"
|
||||
}
|
||||
|
|
@ -20872,7 +20953,6 @@
|
|||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
|
|
@ -21004,7 +21084,6 @@
|
|||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -21018,7 +21097,6 @@
|
|||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
|
|
@ -21171,7 +21249,6 @@
|
|||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
|
||||
"integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.3.4",
|
||||
"@vue/compiler-sfc": "3.3.4",
|
||||
|
|
@ -21482,7 +21559,6 @@
|
|||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
|
||||
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.5",
|
||||
"@webassemblyjs/ast": "^1.12.1",
|
||||
|
|
@ -21698,7 +21774,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
|
||||
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
|
|
@ -21807,7 +21882,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
|
||||
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
|
|
@ -21874,7 +21948,6 @@
|
|||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
|
|
@ -22047,6 +22120,7 @@
|
|||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
|
|
@ -22182,7 +22256,6 @@
|
|||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
|
|
|||
3
playwright-tests/.npmrc
Normal file
3
playwright-tests/.npmrc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
registry=https://pkgs.dev.azure.com/Safelite/Digital/_packaging/DigitalQA/npm/registry/
|
||||
|
||||
always-auth=true
|
||||
4013
playwright-tests/package-lock.json
generated
Normal file
4013
playwright-tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
37
playwright-tests/package.json
Normal file
37
playwright-tests/package.json
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "safelite-iss-nextgen-playwright-tests",
|
||||
"version": "1.0.0",
|
||||
"description": "Playwright tests for ISS Nextgen using Safelite Playwright Core",
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "playwright test",
|
||||
"test:headed": "playwright test --headed",
|
||||
"test:debug": "playwright test --debug",
|
||||
"test:smoke": "playwright test --grep @smoke",
|
||||
"test:report": "playwright test --grep @test_report",
|
||||
"report": "playwright show-report",
|
||||
"install": "playwright install",
|
||||
"install:deps": "playwright install-deps"
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"axios-retry": "^4.5.0",
|
||||
"safelite-playwright-core": "^1.0.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^9.8.0",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@types/dotenv-safe": "^8.1.6",
|
||||
"@types/node": "^22.15.32",
|
||||
"dotenv-safe": "^9.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"luxon": "^3.6.1",
|
||||
"ortoni-report": "^3.0.5",
|
||||
"playwright-jira-reporter": "^1.0.16"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
|
@ -1,42 +1,61 @@
|
|||
import { formatDateForFilename } from 'safelite-playwright-core';
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import { JiraReporterConfig } from 'playwright-jira-reporter'
|
||||
import dotenv from 'dotenv-safe';
|
||||
import { OrtoniReportConfig } from "ortoni-report";
|
||||
import * as path from 'path';
|
||||
import path from 'path';
|
||||
|
||||
if (!process.env.CI) {
|
||||
// Environment variables are present in CI environment, no need to read from file
|
||||
if (process.env.NODE_ENV == 'undefined' || process.env.NODE_ENV == null) {
|
||||
dotenv.config({ path: `playwright-tests/.env.dev`, example: 'playwright-tests/.env.example' });
|
||||
}
|
||||
else {
|
||||
dotenv.config({ path: `playwright-tests/.env.${process.env.NODE_ENV}`, example: 'playwright-tests/.env.example' });
|
||||
// Environment variables are present in CI environment, no need to read from file
|
||||
const basePath = __dirname; // This gets the directory where the config file is located
|
||||
|
||||
if (process.env.PLAYWRIGHT_ENV == undefined || process.env.PLAYWRIGHT_ENV == null) {
|
||||
dotenv.config({
|
||||
path: path.join(basePath, '.env.dev'),
|
||||
example: path.join(basePath, '.env.example')
|
||||
});
|
||||
}
|
||||
else {
|
||||
dotenv.config({
|
||||
path: path.join(basePath, `.env.${process.env.PLAYWRIGHT_ENV}`),
|
||||
example: path.join(basePath, '.env.example')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Ortoni config
|
||||
const ortoniReportConfig: OrtoniReportConfig = {
|
||||
open: "never",
|
||||
folderPath: process.env.CI ? 'ortoni-report' : 'test-results',
|
||||
title: "ISS-NextGen Test Report",
|
||||
filename: `iss_nextgen_ortoni_report_${formatDateForFilename(new Date())}.html`,
|
||||
showProject: false,
|
||||
projectName: "ISS-NextGen-Playwright-Report",
|
||||
testType: `E2E- Environment: ${process.env.PLAYWRIGHT_ENV} `,
|
||||
preferredTheme: "light",
|
||||
base64Image: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// import dotenv from 'dotenv';
|
||||
// import path from 'path';
|
||||
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const reportConfig: OrtoniReportConfig = {
|
||||
port: 1994,
|
||||
open: "never",
|
||||
folderPath: "test-results",
|
||||
filename: "index.html",
|
||||
logo: "../business-logic/data/logo.png",
|
||||
title: "Test Report",
|
||||
showProject: false,
|
||||
projectName: "ISS-Nextgen-Playwright-Report",
|
||||
testType: `E2E- Environment: ${process.env.NODE_ENV} `,
|
||||
preferredTheme: "light",
|
||||
base64Image: true,
|
||||
// Jira Report config
|
||||
const jiraReportConfig: JiraReporterConfig = {
|
||||
// Jira Reporter Config
|
||||
isRegressionRun: process.env.IS_REGRESSION === 'true',
|
||||
jiraProjectKey: process.env.JIRA_PROJECT_KEY || '',
|
||||
jiraEpicKey: process.env.JIRA_EPIC_KEY || '',
|
||||
jiraCardNumber: process.env.JIRA_CARD_NUMBER || '',
|
||||
applicationName: 'ISS NextGen',
|
||||
jiraApiUtilConfig: {
|
||||
jiraUrl: process.env.JIRA_SERVER || '',
|
||||
jiraUsername: process.env.JIRA_USERNAME || '',
|
||||
jiraApiKey: process.env.JIRA_API_KEY || '',
|
||||
jiraBoardId: process.env.JIRA_BOARD_ID || ''
|
||||
},
|
||||
jiraCreationPermissions: {
|
||||
isCreateTestSubtasks: process.env.IS_REGRESSION !== 'true',
|
||||
isCreateBugs: process.env.IS_REGRESSION !== 'true'
|
||||
},
|
||||
// Wrapped ortoni config
|
||||
...ortoniReportConfig
|
||||
};
|
||||
|
||||
export const reportFilePath = path.resolve(__dirname, './../test-results/accessibility-report.html');
|
||||
|
|
@ -53,12 +72,16 @@ export default defineConfig({
|
|||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 4 : 5,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
['ortoni-report', reportConfig],
|
||||
reporter: process.env.CI? [
|
||||
['junit'],
|
||||
['playwright-jira-reporter', jiraReportConfig],
|
||||
['ortoni-report', ortoniReportConfig]
|
||||
]: [
|
||||
['ortoni-report', ortoniReportConfig],
|
||||
['junit'],
|
||||
['list']
|
||||
],
|
||||
timeout: 120_000,
|
||||
timeout: 240_000,
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
|
|
@ -68,52 +91,15 @@ export default defineConfig({
|
|||
trace: 'on-first-retry',
|
||||
headless: process.env.CI ? true : false,
|
||||
screenshot: "only-on-failure",
|
||||
actionTimeout: 5_000,
|
||||
navigationTimeout: 20_000
|
||||
actionTimeout: 60_000,
|
||||
navigationTimeout: 60_000
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'] },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
});
|
||||
3
src/assets/img/icons/alert-circle.svg
Normal file
3
src/assets/img/icons/alert-circle.svg
Normal 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 |
3
src/assets/img/icons/chevron-up.svg
Normal file
3
src/assets/img/icons/chevron-up.svg
Normal 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 |
BIN
src/assets/img/icons/icon-ablue-next.png
Normal file
BIN
src/assets/img/icons/icon-ablue-next.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 494 B |
BIN
src/assets/img/icons/icon-ablue-previous.png
Normal file
BIN
src/assets/img/icons/icon-ablue-previous.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 510 B |
|
|
@ -1,9 +0,0 @@
|
|||
const buttonVariants = Object.freeze({
|
||||
primary: 'primary',
|
||||
secondary: 'secondary',
|
||||
success: 'success',
|
||||
link: 'link',
|
||||
navigation: 'navigation'
|
||||
});
|
||||
|
||||
export default buttonVariants;
|
||||
15
src/constants/component-variants.js
Normal file
15
src/constants/component-variants.js
Normal 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'
|
||||
});
|
||||
|
|
@ -23,13 +23,18 @@ const errorMessages = Object.freeze({
|
|||
LAST_NAME_REQUIRED: 'Please enter your last name',
|
||||
EMAIL_ADDRESS_REQUIRED: 'Email is required.',
|
||||
EMAIL_ADDRESS_FORMAT: 'Please enter a valid email address.',
|
||||
SERVICE_ZIP_REQUIRED: 'Please enter your service ZIP',
|
||||
SERVICE_ZIP_FORMAT: 'Please enter a valid service ZIP',
|
||||
SERVICE_ZIP_REQUIRED: 'ZIP code is required',
|
||||
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_FORMAT:
|
||||
// eslint-disable-next-line max-len
|
||||
'Invalid VIN. Please make sure that you entered the correct 17-digit, alpha-numeric number. VINs do not contain the letters I, O, or Q',
|
||||
OPTION_REQUIRED: 'Please choose an option',
|
||||
OPTION_REQUIRED: 'Please select an option',
|
||||
VEHICLE_REQUIRED: 'Please select a vehicle',
|
||||
POLICY_NUMBER_REQUIRED: 'Policy number is required.',
|
||||
POLICY_NUMBER_FORMAT: 'Please enter an alpha-numeric string',
|
||||
|
|
@ -59,7 +64,8 @@ const errorMessages = Object.freeze({
|
|||
MODEL_REQUIRED: 'Vehicle model is required.',
|
||||
STYLE_REQUIRED: 'Vehicle style is required.',
|
||||
MOBILE_LOCATION_REQUIRED: 'Please enter your service address',
|
||||
DATE_REQUIRED: 'Please select a date'
|
||||
DATE_REQUIRED: 'Please select a date',
|
||||
TIME_REQUIRED: 'Please select an appointment time.'
|
||||
});
|
||||
|
||||
export default errorMessages;
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ export const pageProgressMapper = {
|
|||
percent: 15
|
||||
},
|
||||
'policy-vehicles': {
|
||||
percent: 15
|
||||
percent: 20
|
||||
},
|
||||
'policy-endorsements': {
|
||||
percent: 15
|
||||
percent: 20
|
||||
},
|
||||
'vehicle-selection': {
|
||||
percent: 20
|
||||
|
|
@ -71,6 +71,9 @@ export const pageProgressMapper = {
|
|||
'tpa-search': {
|
||||
percent: 85
|
||||
},
|
||||
'tpa-submit': {
|
||||
percent: 95
|
||||
},
|
||||
'tpa-confirmation': {
|
||||
percent: 100
|
||||
},
|
||||
|
|
|
|||
|
|
@ -294,6 +294,11 @@ export default {
|
|||
this.resetField({
|
||||
value: newValue
|
||||
});
|
||||
},
|
||||
answers() {
|
||||
this.resetField({
|
||||
value: this.modelValue
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,8 +2,9 @@
|
|||
<div
|
||||
class="dropdown-question"
|
||||
:class="{
|
||||
'has-error' : (meta.touched && errors && errors.length && !isDisabled) || hasError,
|
||||
'has-success': (meta.touched && (!errors || !errors.length) && !isDisabled)
|
||||
'has-error' : (meta?.touched && errors && errors.length && !isDisabled) || hasError,
|
||||
'has-success': (meta?.touched && (!errors || !errors.length) && !isDisabled),
|
||||
[`dropdown-${variant}`]: true
|
||||
}">
|
||||
<label
|
||||
:for="inputId"
|
||||
|
|
@ -34,7 +35,7 @@
|
|||
</option>
|
||||
</select>
|
||||
<div
|
||||
v-show="meta.touched && errorMessage && !isDisabled"
|
||||
v-show="meta?.touched && errorMessage && !isDisabled"
|
||||
class="row mt-1 form-test-error">
|
||||
<span role="alert">{{ errorMessage }}</span>
|
||||
</div>
|
||||
|
|
@ -42,6 +43,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { dropdownVariants } from '@/constants/component-variants';
|
||||
import { useField } from 'vee-validate';
|
||||
|
||||
export default {
|
||||
|
|
@ -59,7 +61,11 @@ export default {
|
|||
validationRules: String,
|
||||
cmsWidgetName: String,
|
||||
hasError: Boolean,
|
||||
placeHolderText: String
|
||||
placeHolderText: String,
|
||||
variant: {
|
||||
type: String,
|
||||
default: dropdownVariants.primary
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props) {
|
||||
|
|
@ -140,7 +146,7 @@ export default {
|
|||
background-size: 0.625rem 1rem;
|
||||
color: $gray-600;
|
||||
border-radius: $border-radius;
|
||||
min-height: 3rem;
|
||||
min-height: 2.8125rem;
|
||||
letter-spacing: inherit;
|
||||
box-shadow: $box-shadow-input;
|
||||
&:focus,
|
||||
|
|
@ -154,5 +160,30 @@ export default {
|
|||
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>
|
||||
|
|
|
|||
|
|
@ -13,19 +13,19 @@
|
|||
}">
|
||||
<div class="modal-dialog"
|
||||
:class="{
|
||||
'modal-dialog-edge': modalPosition === 'edge',
|
||||
'modal-dialog-centered': modalPosition === 'center'
|
||||
'modal-dialog-edge': modalPosition === modalPositions.edge,
|
||||
'modal-dialog-centered': modalPosition === modalPositions.center
|
||||
}">
|
||||
<div
|
||||
class="modal-content"
|
||||
role="dialog"
|
||||
aria-modal="true">
|
||||
<div class="modal-header mb-2 mt-2">
|
||||
<label
|
||||
<span
|
||||
v-if="headerText"
|
||||
class="modal-title d-flex justify-content-center pb-0 w-100">
|
||||
{{ headerText }}
|
||||
</label>
|
||||
class="modal-title d-flex justify-content-center pb-0 w-100"
|
||||
:innerHTML="headerText">
|
||||
</span>
|
||||
<button
|
||||
ref="closeButton"
|
||||
type="button"
|
||||
|
|
@ -60,6 +60,7 @@
|
|||
import ButtonMain from '@/ux-components/button-main/button-main.vue';
|
||||
import { Modal } from 'bootstrap';
|
||||
import { useForm } from 'vee-validate';
|
||||
import { modalPositions } from '@/constants/component-variants';
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
|
|
@ -84,7 +85,11 @@ export default {
|
|||
},
|
||||
modalPosition: {
|
||||
type: String,
|
||||
default: 'edge'
|
||||
default: modalPositions.edge
|
||||
},
|
||||
allowInvalidSubmit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['footer-button-event', 'isModalOpened'],
|
||||
|
|
@ -102,7 +107,8 @@ export default {
|
|||
modalId,
|
||||
meta,
|
||||
validate,
|
||||
resetForm
|
||||
resetForm,
|
||||
modalPositions
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -121,7 +127,7 @@ export default {
|
|||
methods: {
|
||||
async validateAndEmit() {
|
||||
const validationResult = await this.validate();
|
||||
if (validationResult.valid) {
|
||||
if (validationResult.valid || this.allowInvalidSubmit) {
|
||||
this.$emit('footer-button-event');
|
||||
}
|
||||
},
|
||||
|
|
@ -257,6 +263,9 @@ export default {
|
|||
border-radius: 1.5rem;
|
||||
overflow: auto;
|
||||
}
|
||||
.modal-header {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.show .modal-dialog {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export default {
|
|||
props: {
|
||||
customText: String, // used to allow the insert of token values into textblock
|
||||
justifyText: String, // left, right, center
|
||||
typeStyle: String, // h1-h6, body, small, label, caption
|
||||
typeStyle: String, // h1-h6, body, small, label, caption, disclaimer
|
||||
// (see Figma or Confluence documentation)
|
||||
fontWeight: String, // bold=500, default is 400
|
||||
cmsWidgetName: String,
|
||||
|
|
@ -109,5 +109,12 @@ export default {
|
|||
&.dark {
|
||||
color: $black;
|
||||
}
|
||||
&.disclaimer {
|
||||
color: $lighter-gray;
|
||||
font-weight: $font-weight-light;
|
||||
font-size: .8125rem;
|
||||
font-style: italic;
|
||||
margin-top: .625rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
class="input-wrapper"
|
||||
:class="[
|
||||
includeSearchIcon ? 'has-search-icon' : '',
|
||||
includeSelectIcon ? 'has-select-icon' : ''
|
||||
includeSelectIcon ? 'has-select-icon' : '',
|
||||
hasTextButton ? 'has-text-button' : ''
|
||||
]">
|
||||
<input
|
||||
:id="inputId"
|
||||
|
|
@ -61,6 +62,13 @@
|
|||
data-bs-toggle="modal"
|
||||
:data-bs-target="'#' + cmsWidgetName"
|
||||
aria-label="Select button" />
|
||||
<buttonMain
|
||||
v-if="hasTextButton"
|
||||
ref="buttonMain"
|
||||
variant="primary"
|
||||
:buttonText="buttonText"
|
||||
@clickEvent="clickedTextButton">
|
||||
</buttonMain>
|
||||
</div>
|
||||
<div
|
||||
v-show="errorMessage"
|
||||
|
|
@ -76,9 +84,13 @@
|
|||
<script>
|
||||
import { useField, validate } from 'vee-validate';
|
||||
import { extractSpanText } from '@/helpers/text-helper';
|
||||
import buttonMain from '@/ux-components/button-main/button-main.vue';
|
||||
|
||||
export default {
|
||||
name: 'textbox-question',
|
||||
components: {
|
||||
buttonMain
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
|
|
@ -118,7 +130,11 @@ export default {
|
|||
includeSelectIcon: Boolean,
|
||||
min: String,
|
||||
max: String,
|
||||
disableAutoFill: Boolean
|
||||
disableAutoFill: Boolean,
|
||||
buttonCallback: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
emits: ['focus', 'update:modelValue', 'textboxQuestionEvent.inputIdAssigned', 'click-event'],
|
||||
setup(props) {
|
||||
|
|
@ -161,6 +177,12 @@ export default {
|
|||
questionTextForAria() {
|
||||
return extractSpanText(this.getCmsContent(this.cmsWidgetName, 'QuestionText'));
|
||||
},
|
||||
buttonText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, 'ButtonText');
|
||||
},
|
||||
hasTextButton() {
|
||||
return this.buttonText && this.buttonText.length > 0;
|
||||
},
|
||||
value: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
|
|
@ -227,6 +249,13 @@ export default {
|
|||
if (this.meta.valid) {
|
||||
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;
|
||||
}
|
||||
.form-test-error span {
|
||||
color: #d4281c;
|
||||
color: $red;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
|
@ -318,6 +347,19 @@ input::-webkit-date-and-time-value {
|
|||
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 {
|
||||
&.has-icon {
|
||||
|
|
@ -338,8 +380,8 @@ input::-webkit-date-and-time-value {
|
|||
.form-control {
|
||||
border: $border-input;
|
||||
border-radius: $border-radius;
|
||||
min-height: 3rem;
|
||||
max-height: 3rem;
|
||||
min-height: 2.8125rem;
|
||||
max-height: 2.8125rem;
|
||||
padding: 0.75rem 1rem;
|
||||
letter-spacing: inherit;
|
||||
box-shadow: $box-shadow-input;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,21 @@ export function convertDateToDateString(date) {
|
|||
);
|
||||
}
|
||||
|
||||
export function convertDateToTwoDigitDay(date) {
|
||||
if (date instanceof Date !== true) return null;
|
||||
return (`0${date.getDate()}`).slice(-2);
|
||||
}
|
||||
|
||||
export function convertDateToTwoDigitMonth(date) {
|
||||
if (date instanceof Date !== true) return null;
|
||||
return (`0${date.getMonth() + 1}`).slice(-2);
|
||||
}
|
||||
|
||||
export function convertDateToShortMonth(date) {
|
||||
if (date instanceof Date !== true) return null;
|
||||
return date.toLocaleString('en-US', { month: 'short' });
|
||||
}
|
||||
|
||||
export function convertDateStringToDate(dateString) {
|
||||
// dateString must be YYYY-MM-DD format
|
||||
if (typeof dateString !== 'string') return null;
|
||||
|
|
@ -51,6 +66,12 @@ export function getDisplayTextForDurationLength(durationMinimum, durationMaximum
|
|||
return `${durationText} ${unitText}`;
|
||||
}
|
||||
|
||||
export function isAfternoon(timeString) {
|
||||
if (typeof timeString !== 'string') return false;
|
||||
const hours = parseInt(timeString.split(':')[0], 10);
|
||||
return hours >= 12;
|
||||
}
|
||||
|
||||
export function militaryToTwelveHourTime(timeString) {
|
||||
// Expected input: "HH:MM"
|
||||
if (typeof timeString !== 'string') return null;
|
||||
|
|
@ -163,9 +184,11 @@ export function combineDateAndTime(date, time) {
|
|||
// Return the new date object
|
||||
return newDate;
|
||||
}
|
||||
|
||||
export function addMinutes(date, minutes) {
|
||||
return new Date(date.getTime() + minutes * 60000);
|
||||
}
|
||||
|
||||
export function shortTimeString(date) {
|
||||
// Use a ternary operator to check if the input is a valid date object
|
||||
return date instanceof Date
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function defineGlobalPhoneNumberRules() {
|
|||
function defineGlobalExtensionRules() {
|
||||
defineRule(
|
||||
globalRules.EXTENSION_FORMAT,
|
||||
regex(/^[0-9]{5}$/, errorMessages.EXTENSION_FORMAT)
|
||||
regex(/^[0-9]{1,5}$/, errorMessages.EXTENSION_FORMAT)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ function processZipCodeResults(request) {
|
|||
containsMilitaryBase: serviceZipValidationResponse.containsMilitaryBase,
|
||||
isValid: serviceZipValidationResponse.isValid,
|
||||
isServiceable: serviceZipValidationResponse.isServiceable,
|
||||
city: serviceZipValidationResponse.city,
|
||||
state: serviceZipValidationResponse.state,
|
||||
zipCodeCtu: serviceZipValidationResponse.zipCodeCtu
|
||||
})).catch(() => ({
|
||||
|
|
|
|||
|
|
@ -34,11 +34,8 @@ export function createOrderedListFromStringOfParagraphs(stringOfParagraphs) {
|
|||
* @returns {string}
|
||||
*/
|
||||
export function toTitleCase(text) {
|
||||
const temp = text?.toLowerCase()?.split(' ') ?? [];
|
||||
for (let i = 0; i < temp.length; i++) {
|
||||
temp[i] = temp[i].charAt(0).toUpperCase() + temp[i].slice(1);
|
||||
}
|
||||
return temp.join(' ');
|
||||
let temp = text?.toLowerCase() ?? '';
|
||||
return temp.replace(/(^|\s|-)\S/g, (letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@
|
|||
hasIcon
|
||||
disableAutoFill
|
||||
validationRules="street-address-required"
|
||||
@keydown.enter.prevent
|
||||
class="mb-4" />
|
||||
@keydown.enter.prevent />
|
||||
</div>
|
||||
</div>
|
||||
<transition
|
||||
|
|
@ -29,24 +28,22 @@
|
|||
v-model="addressModel.streetAddress2"
|
||||
cmsWidgetName="StreetAddress2QuestionWidget"
|
||||
aria-haspopup=""
|
||||
inputId="streetAddress2Field"
|
||||
class="mb-4" />
|
||||
inputId="streetAddress2Field" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
aria-live="polite">
|
||||
<div class="col">
|
||||
<div class="col-lg-6">
|
||||
<textboxQuestion
|
||||
ref="city"
|
||||
v-model="addressModel.city"
|
||||
cmsWidgetName="CityQuestionWidget"
|
||||
inputId="cbf28188fdf2436688fd735915f7ee56"
|
||||
disableAutoFill
|
||||
validationRules="city-required"
|
||||
class="mb-4" />
|
||||
validationRules="city-required" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="col-lg-6">
|
||||
<dropdownQuestion
|
||||
ref="state"
|
||||
v-model="addressModel.state"
|
||||
|
|
@ -55,8 +52,7 @@
|
|||
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
|
||||
:options="stateOptions"
|
||||
disableAutoFill
|
||||
validationRules="state-required"
|
||||
class="mb-4" />
|
||||
validationRules="state-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -70,8 +66,7 @@
|
|||
inputId="01a9a1c2de0b4c9da8e023c9ae3be498"
|
||||
mask="#####"
|
||||
disableAutoFill
|
||||
validationRules="zip-code-required|zip-code-format"
|
||||
class="mb-4" />
|
||||
validationRules="zip-code-required|zip-code-format" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -362,3 +357,12 @@ export default {
|
|||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.textbox-question {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.dropdown-question {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
<a
|
||||
href=""
|
||||
target="_blank"
|
||||
@click="openCookiePreferences">Cookie Preferences</a>
|
||||
@click="openCookiePreferences">Cookie preferences</a>
|
||||
</div>
|
||||
<div class="footer-menu-item">
|
||||
<textLink
|
||||
|
|
|
|||
|
|
@ -314,7 +314,6 @@ describe('Google Map', () => {
|
|||
// Arrange
|
||||
const wrapper = shallowMount(googleMap, {});
|
||||
await awaitingSetupTicks(wrapper);
|
||||
const numberOfExtendCallsDuringMount = 2;
|
||||
|
||||
mockGeocode.mockImplementation(() => {
|
||||
const result = {
|
||||
|
|
@ -329,7 +328,7 @@ describe('Google Map', () => {
|
|||
const result = wrapper.vm.getBounds(locations);
|
||||
|
||||
// Assert
|
||||
expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCallsDuringMount + numberOfExtendCallsPostMount);
|
||||
expect(mockExtend).toHaveBeenCalledTimes(numberOfExtendCallsPostMount);
|
||||
expect(result).toBe(mockLatLngBound);
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export default {
|
|||
const { AdvancedMarkerElement, PinElement } = await window.google.maps.importLibrary('marker');
|
||||
markers?.forEach((marker, index) => {
|
||||
const pinElement = new PinElement({
|
||||
glyph: String.fromCharCode('A'.charCodeAt(0) + index),
|
||||
glyphText: String.fromCharCode('A'.charCodeAt(0) + index),
|
||||
glyphColor: '#000000',
|
||||
borderColor: '#000000'
|
||||
});
|
||||
|
|
@ -107,9 +107,11 @@ export default {
|
|||
async getBoundsFromAddress(address) {
|
||||
await this.setGeocoder();
|
||||
const result = await this.geocoder.geocode({ address });
|
||||
return result?.results?.length > 0
|
||||
? result.results[0].geometry?.bounds
|
||||
: null;
|
||||
if (result?.results?.length > 0) {
|
||||
return result.results[0].geometry?.bounds ?? null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async createMapWithMarkersForAddresses(markers) {
|
||||
const markerPositions = await this.getLocationsFromAddresses(markers);
|
||||
|
|
@ -117,9 +119,12 @@ export default {
|
|||
const map = await this.getMap(zipBounds?.getCenter());
|
||||
|
||||
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);
|
||||
map.fitBounds(bounds);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
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">
|
||||
<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>
|
||||
`;
|
||||
|
||||
exports[`Shop list button should render correctly with required props 1`] = `
|
||||
<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>
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -149,10 +149,9 @@ describe('Shop list button', () => {
|
|||
|
||||
// Assert
|
||||
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('caption');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('ms-2');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('button-label-sub-copy');
|
||||
expect(buttonLabelSubCopy.classes()).toContain(textPosition);
|
||||
});
|
||||
test('when "textPosition" prop not provided', async () => {
|
||||
|
|
@ -173,10 +172,9 @@ describe('Shop list button', () => {
|
|||
|
||||
// Assert
|
||||
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('caption');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('ms-2');
|
||||
expect(buttonLabelSubCopy.classes()).toContain('button-label-sub-copy');
|
||||
});
|
||||
});
|
||||
describe('availability indicator block with expected when displayAvailabilityIndicators true', () => {
|
||||
|
|
@ -378,8 +376,7 @@ describe('Shop list button', () => {
|
|||
// Assert
|
||||
expect(buttonBodyCopy.exists()).toBeTruthy();
|
||||
expect(buttonBodyCopy.classes()).toContain('m-0');
|
||||
expect(buttonBodyCopy.classes()).toContain('button-label-sub-copy');
|
||||
expect(buttonBodyCopy.classes()).toContain('small');
|
||||
expect(buttonBodyCopy.classes()).toContain('button-body-copy');
|
||||
});
|
||||
test('screen reader only text when screenReaderOnlyText provided', async () => {
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -5,35 +5,39 @@
|
|||
<baseInputButton
|
||||
v-bind="$props"
|
||||
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
|
||||
:aria-label="buttonLabel"
|
||||
class="button-content list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
||||
<div class="row-one">
|
||||
<div class="availability-indicator-spacer"></div>
|
||||
<span
|
||||
id="buttonLabelSpan"
|
||||
class="m-0 button-label-copy"
|
||||
:class="textPosition">{{ buttonLabel }}
|
||||
</span>
|
||||
<span class="separator"></span>
|
||||
<span
|
||||
id="buttonLabelSubCopySpan"
|
||||
class="m-0 caption ms-2"
|
||||
class="m-0 button-label-sub-copy"
|
||||
:class="textPosition">{{ buttonLabelSubCopy }}
|
||||
</span>
|
||||
<div
|
||||
v-if="displayAvailabilityIndicators"
|
||||
id="availabilityIndicatorBlock"
|
||||
class="availability-indicator rounded-pill"
|
||||
:class="availabilityRatingClass">
|
||||
<div class="availability-indicator-container">
|
||||
<div
|
||||
id="availabilityIndicator"
|
||||
class="d-flex align-items-center">
|
||||
v-if="displayAvailabilityIndicators"
|
||||
id="availabilityIndicatorBlock"
|
||||
class="availability-indicator rounded-pill"
|
||||
:class="availabilityRatingClass">
|
||||
<div
|
||||
id="availabilityBadge"
|
||||
class="availability-badge"
|
||||
:class="availabilityRating == 'high' ? 'green' : 'orange'">
|
||||
id="availabilityIndicator"
|
||||
class="d-flex align-items-center">
|
||||
<div
|
||||
id="availabilityBadge"
|
||||
class="availability-badge"
|
||||
:class="availabilityRatingClass">
|
||||
</div>
|
||||
<span class="m-0 button-auxillary-copy">{{ badgeText }}</span>
|
||||
</div>
|
||||
<span class="m-0 button-auxillary-copy">{{ badgeText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -41,7 +45,7 @@
|
|||
<span
|
||||
v-if="buttonBodyCopy"
|
||||
id="buttonBodyCopy"
|
||||
class="m-0 button-label-sub-copy small"
|
||||
class="m-0 button-body-copy"
|
||||
v-html="buttonBodyCopy"></span>
|
||||
</div>
|
||||
<span
|
||||
|
|
@ -103,116 +107,128 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
@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 {
|
||||
outline: none;
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
position: static; //override bootstrap
|
||||
|
||||
&:focus-visible + .list-button-content {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:focus + .list-button-content {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:checked + .list-button-content {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
background: $blue-100;
|
||||
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;
|
||||
}
|
||||
}
|
||||
outline: none;
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
position: static; //override bootstrap
|
||||
&:checked + .list-button-content {
|
||||
background: $heritage-checked-background-color;
|
||||
border-color: $heritage-checked-border-color;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
.button-label-copy {
|
||||
font-weight: 500;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
&:focus:checked + .list-button-content {
|
||||
border-width: 2.5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.list-button-content {
|
||||
color: $gray-600;
|
||||
position: relative;
|
||||
background: $white;
|
||||
transition: all 150ms linear;
|
||||
border-radius: $border-radius-lg;
|
||||
border: 1px solid $gray-500;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
|
||||
span {
|
||||
&.small {
|
||||
font-size: 0.75rem;
|
||||
color: $gray-550;
|
||||
}
|
||||
}
|
||||
color: $darker-gray;
|
||||
font-weight: $font-weight-normal;
|
||||
position: relative;
|
||||
background: $white;
|
||||
transition: all 150ms linear;
|
||||
border-radius: $heritage-border-radius;
|
||||
border: 1px solid $heritage-border-color;
|
||||
box-shadow: $heritage-box-shadow;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
}
|
||||
.button-content {
|
||||
row-gap: 0.25rem;
|
||||
row-gap: 0.25rem;
|
||||
|
||||
.row-one {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1.5rem;
|
||||
.row-one {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1.5rem;
|
||||
|
||||
.button-label-copy {
|
||||
font-weight: 500;
|
||||
}
|
||||
.separator {
|
||||
height: 1.25rem;
|
||||
margin: 0rem .3125rem;
|
||||
border-left: 1px solid $heritage-border-color;
|
||||
}
|
||||
|
||||
.availability-indicator {
|
||||
background-repeat: no-repeat;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
padding: 0.125rem 0.5rem;
|
||||
.availability-indicator-spacer {
|
||||
width: 8rem;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.availability-badge {
|
||||
display: inline-flex;
|
||||
width: 13px;
|
||||
height: 12px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
margin: 0 0.25rem 0 0;
|
||||
.availability-indicator-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
&.green {
|
||||
background-image: url($svg-shop-list-button-green-availability);
|
||||
}
|
||||
.availability-indicator {
|
||||
background-repeat: no-repeat;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.125rem 0.5rem;
|
||||
|
||||
&.orange {
|
||||
background-image: url($svg-shop-list-button-orange-availability);
|
||||
}
|
||||
}
|
||||
.button-auxillary-copy {
|
||||
box-sizing: border-box;
|
||||
justify-content: right;
|
||||
line-height: 1.25rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
.availability-badge {
|
||||
display: inline-flex;
|
||||
width: 13px;
|
||||
height: 12px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
margin: 0 0.25rem 0 0;
|
||||
|
||||
&.green {
|
||||
color: $green-700;
|
||||
background-color: $green-100;
|
||||
}
|
||||
&.green {
|
||||
background-image: url($svg-shop-list-button-green-availability);
|
||||
}
|
||||
|
||||
&.orange {
|
||||
color: $orange-600;
|
||||
background-color: $orange-100;
|
||||
}
|
||||
&.orange {
|
||||
background-image: url($svg-shop-list-button-orange-availability);
|
||||
}
|
||||
|
||||
&.gray {
|
||||
color: $gray-600;
|
||||
background-color: $gray-100;
|
||||
padding-left: 0.125rem;
|
||||
padding-right: 0.125rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.gray {
|
||||
background-image: url(~@/assets/img/heritage-loader-blue.gif);
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
.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 {
|
||||
text-align: left;
|
||||
}
|
||||
&.green {
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@
|
|||
"
|
||||
:aria-disabled="disableForwardAction || isForwardActionDisabled"
|
||||
:isDisabled="disableForwardAction || isForwardActionDisabled"
|
||||
data-bs-target="#footerModal"
|
||||
data-bs-dismiss="modal"
|
||||
data-test-id="site-footer-main-button"
|
||||
@clickEvent="buttonClick" />
|
||||
</div>
|
||||
|
|
@ -36,8 +34,6 @@
|
|||
linkType="navigation"
|
||||
:text="backLink"
|
||||
href="javascript:void(0)"
|
||||
data-bs-target="#footerModal"
|
||||
data-bs-dismiss="modal"
|
||||
data-test-id="site-footer-back-button"
|
||||
@clickEvent="linkClick" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import menuModal from '@/iss-components/site-header/menu-modal/menu-modal.vue';
|
||||
|
||||
describe('menu-modal.vue', () => {
|
||||
it('Should return text Footer Navigation', async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(menuModal);
|
||||
|
||||
// Assert
|
||||
const footerModalLabel = wrapper.find('h5');
|
||||
|
||||
// Expect
|
||||
expect(footerModalLabel.text()).toContain('Footer Navigation');
|
||||
});
|
||||
|
||||
it('Should return footer text as Safelite Group', async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(menuModal);
|
||||
|
||||
// Assert
|
||||
const modalFooter = wrapper.find('div.modal-footer');
|
||||
|
||||
// Expect
|
||||
expect(modalFooter.text()).toContain('Safelite Group');
|
||||
});
|
||||
|
||||
it('Should return Terms of use text link text', async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(menuModal);
|
||||
|
||||
// Expect
|
||||
expect(wrapper.html()).toContain('Terms of service');
|
||||
});
|
||||
|
||||
it('Should return "Your privacy choices" text link text', async () => {
|
||||
// Act
|
||||
const wrapper = mount(menuModal);
|
||||
|
||||
// Expect
|
||||
expect(wrapper.html()).toContain('Your privacy choices');
|
||||
});
|
||||
|
||||
it('Should return Warranty text link text', async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(menuModal);
|
||||
|
||||
// Expect
|
||||
expect(wrapper.html()).toContain('Warranty');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
<template>
|
||||
<div class="menu-modal-container">
|
||||
<button
|
||||
class="menu-button"
|
||||
type="button"
|
||||
:class="[isActive ? 'active' : '']"
|
||||
aria-label="Hamburger Menu (modal window)"
|
||||
@click="toggleModal">
|
||||
<div class="bar1"></div>
|
||||
<div class="bar2"></div>
|
||||
<div class="bar3"></div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="menu-modal-container">
|
||||
<button
|
||||
aria-hidden="true"
|
||||
tabindex="-1"
|
||||
class="menu-button"
|
||||
type="button"
|
||||
:class="[isActive ? 'active' : '']"
|
||||
aria-label="Hamburger Menu (modal window)"
|
||||
@click="toggleModal">
|
||||
<div class="bar1"></div>
|
||||
<div class="bar2"></div>
|
||||
<div class="bar3"></div>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Modal -->
|
||||
<div
|
||||
id="footerModal"
|
||||
class="modal menu-modal fade"
|
||||
data-bs-backdrop="false"
|
||||
tabindex="-1"
|
||||
aria-labelledby="footerModalLabel"
|
||||
aria-hidden="true"
|
||||
v-on="{ 'show.bs.modal': show, 'hide.bs.modal': hide }">
|
||||
<div class="modal-dialog modal-fullscreen">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header visually-hidden">
|
||||
<h5 id="footerModalLabel" class="modal-title">
|
||||
Footer Navigation
|
||||
</h5>
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<textLink
|
||||
linkType="newWindowLink"
|
||||
text="Terms of service"
|
||||
href="//www.safelite.com/terms-of-use"
|
||||
target="_blank" />
|
||||
<textLink
|
||||
linkType="newWindowLink"
|
||||
text="Your privacy choices"
|
||||
href="//www.safelite.com/privacy-center"
|
||||
target="_blank">
|
||||
<template #after-text>
|
||||
<img
|
||||
class="ccpa-icon"
|
||||
src="~@/assets/img/icons/ccpa-icon.svg"
|
||||
alt="Your privacy choices" />
|
||||
</template>
|
||||
</textLink>
|
||||
<textLink
|
||||
linkType="newWindowLink"
|
||||
text="Warranty"
|
||||
href="//www.safelite.com/national-lifetime-warranty"
|
||||
target="_blank" />
|
||||
<textLink
|
||||
linkType="newWindowLink"
|
||||
text="Notice at collection"
|
||||
href="https://www.safelite.com/ccpa-privacy-policy"
|
||||
target="_blank" />
|
||||
</div>
|
||||
<div class="modal-footer d-flex justify-content-start">
|
||||
© {{ new Date().getFullYear() }} Safelite Group
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import textLink from '@/ux-components/text-link/text-link';
|
||||
import { Modal } from 'bootstrap';
|
||||
import baseMixin from '@/mixins/base-mixin.js';
|
||||
|
||||
export default {
|
||||
name: 'menu-modal',
|
||||
components: {
|
||||
textLink,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleModal() {
|
||||
if (this.isActive) {
|
||||
this.closeModal();
|
||||
} else {
|
||||
this.openModal();
|
||||
}
|
||||
},
|
||||
openModal() {
|
||||
Modal.getOrCreateInstance(
|
||||
document.getElementById('footerModal')
|
||||
).show();
|
||||
},
|
||||
closeModal() {
|
||||
Modal.getInstance(document.getElementById('footerModal')).hide();
|
||||
},
|
||||
show() {
|
||||
this.isActive = true;
|
||||
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
|
||||
},
|
||||
hide() {
|
||||
const self = this;
|
||||
self.isActive = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-modal-container {
|
||||
position: absolute;
|
||||
padding: 1.47rem 0.5rem 1.47rem 1.47rem;
|
||||
right: 0;
|
||||
button {
|
||||
border: none;
|
||||
&.menu-button {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
background-color: $white;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0; //Required to prevent 'squish' on iPhone
|
||||
z-index: 1050;
|
||||
.bar1,
|
||||
.bar2,
|
||||
.bar3 {
|
||||
width: 14px;
|
||||
height: 2px;
|
||||
background-color: $blue;
|
||||
margin: 1px 0;
|
||||
transition: 0.25s;
|
||||
}
|
||||
&.active .bar1 {
|
||||
transform: rotate(-45deg) translate(-3px, 3px);
|
||||
}
|
||||
&.active .bar2 {
|
||||
opacity: 0;
|
||||
}
|
||||
&.active .bar3 {
|
||||
transform: rotate(45deg) translate(-3px, -3px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal {
|
||||
&.menu-modal {
|
||||
left: auto;
|
||||
height: calc(100% - 56px);
|
||||
top: 56px;
|
||||
border-top: 1px solid $gray-300;
|
||||
overflow-x: visible;
|
||||
overflow-y: visible;
|
||||
.modal-body {
|
||||
padding: 2rem;
|
||||
.ccpa-icon {
|
||||
width: 2.0625rem;
|
||||
height: 1rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
.modal-fullscreen {
|
||||
width: 100vw;
|
||||
}
|
||||
.modal-footer {
|
||||
border-top: none;
|
||||
padding: 2rem;
|
||||
}
|
||||
.menu-modal-container {
|
||||
position: absolute;
|
||||
padding: 1.47rem 0 1.47rem 1.47rem;
|
||||
right: 0;
|
||||
top: -5rem;
|
||||
button {
|
||||
border: none;
|
||||
&.menu-button {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
box-shadow: none;
|
||||
background-color: $white;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0; //Required to prevent 'squish' on iPhone
|
||||
z-index: 1056;
|
||||
.bar1,
|
||||
.bar2,
|
||||
.bar3 {
|
||||
width: 14px;
|
||||
height: 2px;
|
||||
background-color: $blue;
|
||||
margin: 1px 0;
|
||||
transition: 0.25s;
|
||||
}
|
||||
&.active .bar1 {
|
||||
transform: rotate(-45deg) translate(-3px, 3px);
|
||||
}
|
||||
&.active .bar2 {
|
||||
opacity: 0;
|
||||
}
|
||||
&.active .bar3 {
|
||||
transform: rotate(45deg) translate(-3px, -3px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//.modal-backdrop styles are in common-styles.scss
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -186,7 +186,7 @@ export default {
|
|||
VehiclesForQuestions() {
|
||||
// Map API result data, to address-vehicles data structure
|
||||
const mappedData = this.VehiclesFromApi.map((v) => {
|
||||
const maskSymbol = 'X';
|
||||
const maskSymbol = '*';
|
||||
const vinStart = maskSymbol.repeat(v.vin.length - 6);
|
||||
const vinEnd = v.vin.substring(v.vin.length - 6);
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
<buttonMain
|
||||
:variant="buttonVariants.primary"
|
||||
buttonText="Start a new claim"
|
||||
class="w-100"
|
||||
class="mt-5 w-100"
|
||||
@clickEvent="startNewClaim" />
|
||||
<siteFooter
|
||||
ref="siteFooter"
|
||||
|
|
@ -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 buttonQuestion from '@/digital-components/button-question/button-question.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
|
||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper.js';
|
||||
|
|
@ -135,7 +135,8 @@ export default {
|
|||
* @summary Steps to perform when forward button clicked.
|
||||
*/
|
||||
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();
|
||||
return;
|
||||
}
|
||||
|
|
@ -154,6 +155,7 @@ export default {
|
|||
}
|
||||
},
|
||||
navigateForward() {
|
||||
this.mainStore.updateDuplicateCheckVisited(true);
|
||||
if (!this.mainStore.isPolicyLookupSuccessful) {
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
|
||||
|
|
@ -221,17 +223,23 @@ export default {
|
|||
}
|
||||
|
||||
.duplicate-check-question {
|
||||
span {
|
||||
font-weight:600;
|
||||
}
|
||||
.question-text {
|
||||
justify-content: left;
|
||||
display: inline-flex !important;
|
||||
margin-top: map-get($spacers, 4);
|
||||
margin-bottom: 0.625rem !important;
|
||||
span {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.form-test-error {
|
||||
margin-top: 0 !important;
|
||||
span {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.question-text.d-flex {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,12 +249,6 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.subheader-secondary {
|
||||
p {
|
||||
margin-bottom: 0.25rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.25rem !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
// Components
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import entryPage from '@/layouts/entry-page/entry-page.vue';
|
||||
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
// Supporting Files
|
||||
import baseMixin from '@/mixins/base-mixin';
|
||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||
import { useMainStore } from '@/store';
|
||||
import settleAllPromises from '@/helpers/layout-helper.js';
|
||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||
import * as clientAuthHelper from '@/helpers/clientauth-helper';
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
|
||||
|
|
@ -15,34 +20,192 @@ jest.mock('@/helpers/cms-content-helper', () => ({
|
|||
setupModalLinks: jest.fn()
|
||||
}));
|
||||
|
||||
/** @ignore */
|
||||
function setupMocks(queryString) {
|
||||
function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRunAfterInitializingStore = () => {}) {
|
||||
const mountOptions = getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn()
|
||||
},
|
||||
route: { queryString }
|
||||
}
|
||||
});
|
||||
|
||||
const wrapper = shallowMount(
|
||||
entryPage,
|
||||
mountOptions
|
||||
);
|
||||
const testingPinia = createTestingPinia({
|
||||
initialState: {
|
||||
main: mainInitialState
|
||||
}
|
||||
});
|
||||
useMainStore(testingPinia);
|
||||
methodToRunAfterInitializingStore();
|
||||
|
||||
const apiResponses = {};
|
||||
mountOptions.global.plugins = [testingPinia];
|
||||
mountOptions.data = () => (initialData);
|
||||
|
||||
const apiResponses = { cmsContent: {} };
|
||||
|
||||
settleAllPromises.mockImplementation(() => apiResponses);
|
||||
fetchCmsContentForPage.mockImplementation(() => { });
|
||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||
|
||||
const wrapper = shallowMount(entryPage, mountOptions);
|
||||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {});
|
||||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.$router.navigateWithSpinner = jest.fn();
|
||||
wrapper.vm.navigateForward = baseMixin.methods.navigateForward;
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
||||
describe('entry-page.vue', () => {
|
||||
test('should render', () => {
|
||||
const queryString = 'policynumber="123456"';
|
||||
const { wrapper } = setupMocks(queryString);
|
||||
test('shows unauthorized message when not authorized', async () => {
|
||||
const wrapper = shallowMount(entryPage, getMountOptions());
|
||||
wrapper.setData({ unauthorized: true });
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('#message').isVisible()).toBe(true);
|
||||
expect(wrapper.text()).toContain('Unauthorized Access.');
|
||||
});
|
||||
describe('validateClientTagOnEntry', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
const mainInitialState = { issConfig: {} };
|
||||
wrapper = getMountedComponent(mainInitialState).wrapper;
|
||||
});
|
||||
|
||||
window.console.log(wrapper.vm.$route.query);
|
||||
expect(wrapper).toBeTruthy();
|
||||
it('returns unauthorized if no clienttag', async () => {
|
||||
const result = await wrapper.vm.validateClientTagOnEntry({});
|
||||
expect(result.isAuthorized).toBe(false);
|
||||
});
|
||||
|
||||
it('returns unauthorized if validateISSClientTag returns falsy', async () => {
|
||||
jest.spyOn(clientAuthHelper, 'validateISSClientTag').mockResolvedValueOnce(null);
|
||||
const result = await wrapper.vm.validateClientTagOnEntry({ clienttag: 'abc' });
|
||||
expect(result.isAuthorized).toBe(false);
|
||||
});
|
||||
|
||||
it('returns authorized and clientData if valid and not RSAToken', async () => {
|
||||
const resp = {
|
||||
active: true,
|
||||
accountName: 'Client',
|
||||
authentication: '',
|
||||
parentAccountNumber: 'P',
|
||||
styleSheet: '',
|
||||
coverageEnabled: true,
|
||||
siteType: ''
|
||||
};
|
||||
jest.spyOn(clientAuthHelper, 'validateISSClientTag').mockResolvedValueOnce(resp);
|
||||
const result = await wrapper.vm.validateClientTagOnEntry({ clienttag: 'abc' });
|
||||
expect(result.isAuthorized).toBe(true);
|
||||
expect(result.clientData).toEqual(resp);
|
||||
});
|
||||
|
||||
it('handles RSAToken with valid signature and EncParams', async () => {
|
||||
const resp = {
|
||||
active: true,
|
||||
accountName: 'Client',
|
||||
authentication: 'RSAToken EncParams',
|
||||
parentAccountNumber: 'P',
|
||||
styleSheet: '',
|
||||
coverageEnabled: true,
|
||||
siteType: ''
|
||||
};
|
||||
const decryptedData = 'foo=bar&from=yesterday';
|
||||
jest.spyOn(clientAuthHelper, 'validateISSClientTag').mockResolvedValueOnce(resp);
|
||||
jest.spyOn(clientAuthHelper, 'validateISSClientSignature').mockResolvedValueOnce({ valid: true, decryptedData });
|
||||
const result = await wrapper.vm.validateClientTagOnEntry({ clienttag: 'abc', token: 'tok', signature: 'sig' });
|
||||
expect(result.isAuthorized).toBe(true);
|
||||
expect(result.clientData).toEqual(resp);
|
||||
expect(result.decryptedParams).toEqual({ foo: 'bar', from: 'yesterday' });
|
||||
});
|
||||
|
||||
it('handles RSAToken with invalid signature', async () => {
|
||||
const resp = {
|
||||
active: true,
|
||||
accountName: 'Client',
|
||||
authentication: 'RSAToken',
|
||||
parentAccountNumber: 'P',
|
||||
styleSheet: '',
|
||||
coverageEnabled: true,
|
||||
siteType: ''
|
||||
};
|
||||
jest.spyOn(clientAuthHelper, 'validateISSClientTag').mockResolvedValueOnce(resp);
|
||||
jest.spyOn(clientAuthHelper, 'validateISSClientSignature').mockResolvedValueOnce({ valid: false });
|
||||
const result = await wrapper.vm.validateClientTagOnEntry({ clienttag: 'abc', token: 'tok', signature: 'sig' });
|
||||
expect(result.isAuthorized).toBe(false);
|
||||
});
|
||||
});
|
||||
test('populateISSConfigValues sets issConfig fields and parses clientFlags', () => {
|
||||
const mainInitialState = {
|
||||
issConfig: {}
|
||||
};
|
||||
const { wrapper } = getMountedComponent(mainInitialState);
|
||||
const data = {
|
||||
accountName: 'TestClient',
|
||||
parentAccountNumber: '12345',
|
||||
styleSheet: 'test-style',
|
||||
coverageEnabled: true,
|
||||
siteType: 'test-site',
|
||||
clientFlags: JSON.stringify({
|
||||
TPAEnabled: true,
|
||||
ClientFullName: 'Full Name',
|
||||
ClientDisplayName: 'Display Name',
|
||||
ClaimRegistrationRequired: true,
|
||||
EnableNoCompQuote: true
|
||||
})
|
||||
};
|
||||
wrapper.vm.populateISSConfigValues(data);
|
||||
const { issConfig } = wrapper.vm.mainStore;
|
||||
expect(issConfig.clientName).toBe('TestClient');
|
||||
expect(issConfig.clientFullName).toBe('Full Name');
|
||||
expect(issConfig.clientDisplayName).toBe('Display Name');
|
||||
expect(issConfig.parentAccountNumber).toBe('12345');
|
||||
expect(issConfig.styleSheet).toBe('test-style');
|
||||
expect(issConfig.isCoverageEnabled).toBe(true);
|
||||
expect(issConfig.siteType).toBe('test-site');
|
||||
expect(issConfig.enableTPAFlow).toBe(true);
|
||||
expect(issConfig.isClaimRegistrationRequired).toBe(true);
|
||||
expect(issConfig.enableNoCompQuote).toBe(true);
|
||||
});
|
||||
describe('combineClientParameters', () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
wrapper = getMountedComponent({ issConfig: {} }).wrapper;
|
||||
});
|
||||
|
||||
it('returns correct params from config and query', () => {
|
||||
const configParams = JSON.stringify(['PolicyNbr', 'DateOfLoss', 'Unused']);
|
||||
const queryStringParams = { policynbr: '123', dateofloss: '2022-01-01', somethingelse: 'no' };
|
||||
const result = wrapper.vm.combineClientParameters(configParams, queryStringParams);
|
||||
expect(result).toEqual({ policynbr: '123', dateofloss: '2022-01-01' });
|
||||
});
|
||||
|
||||
it('returns empty object if configParams is not valid JSON', () => {
|
||||
const configParams = 'notjson';
|
||||
const queryStringParams = { policynbr: '123' };
|
||||
const result = wrapper.vm.combineClientParameters(configParams, queryStringParams);
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('ignores params not present in query', () => {
|
||||
const configParams = JSON.stringify(['PolicyNbr', 'MissingParam']);
|
||||
const queryStringParams = { policynbr: '123' };
|
||||
const result = wrapper.vm.combineClientParameters(configParams, queryStringParams);
|
||||
expect(result).toEqual({ policynbr: '123' });
|
||||
});
|
||||
});
|
||||
test('populates store items from params', async () => {
|
||||
const mainInitialState = {
|
||||
issConfig: { disabledFields: {} },
|
||||
order: { policy: {} }
|
||||
};
|
||||
const params = {
|
||||
policynumber: 'ABC123',
|
||||
policyzipcode: '90210',
|
||||
dateofloss: '2022-01-01',
|
||||
returnurl: 'http://success',
|
||||
returnurl2: 'http://fail'
|
||||
};
|
||||
const { wrapper } = getMountedComponent(mainInitialState);
|
||||
wrapper.vm.populateStoreItemsFromParams(params);
|
||||
expect(wrapper.vm.mainStore.order.policy.policyNumber).toBe('ABC123');
|
||||
expect(wrapper.vm.mainStore.order.policy.policyZipCode).toBe('90210');
|
||||
expect(wrapper.vm.mainStore.order.policy.dateOfLoss).toBe('2022-01-01');
|
||||
expect(wrapper.vm.mainStore.issConfig.successReturnURL).toBe('http://success');
|
||||
expect(wrapper.vm.mainStore.issConfig.failureReturnURL).toBe('http://fail');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ export default {
|
|||
},
|
||||
populateISSConfigValues(data) {
|
||||
this.mainStore.issConfig.clientName = data.accountName;
|
||||
this.mainStore.issConfig.clientFullName = data.accountName; // Defaults to use the client name.
|
||||
this.mainStore.issConfig.clientDisplayName = data.accountName; // Defaults to use the client name.
|
||||
this.mainStore.issConfig.parentAccountNumber = data.parentAccountNumber;
|
||||
this.mainStore.issConfig.styleSheet = data.styleSheet;
|
||||
|
|
@ -157,6 +158,10 @@ export default {
|
|||
this.mainStore.issConfig.enableTPAFlow = true;
|
||||
}
|
||||
|
||||
if (clientFlags.ClientFullName != null) {
|
||||
this.mainStore.issConfig.clientFullName = clientFlags.ClientFullName;
|
||||
}
|
||||
|
||||
if (clientFlags.ClientDisplayName != null) {
|
||||
this.mainStore.issConfig.clientDisplayName = clientFlags.ClientDisplayName;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<siteSubHeader
|
||||
ref="siteSubHeader"
|
||||
cmsWidgetName="SiteSubHeaderWidget"
|
||||
class="mt-4" />
|
||||
class="site-sub-header-container" />
|
||||
<buttonQuestion
|
||||
v-if="educatorEndorsement"
|
||||
ref="schoolPropertyQuestion"
|
||||
|
|
@ -192,17 +192,147 @@ export default {
|
|||
min-height: 1px;
|
||||
padding-left: .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) {
|
||||
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) {
|
||||
line-height: 1.5rem;
|
||||
margin-top: 0.625rem;
|
||||
p {
|
||||
font-size: $h6-font-size;
|
||||
line-height: map-get($spacers, 5);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -213,18 +343,6 @@ export default {
|
|||
justify-content: left !important;
|
||||
padding-left: 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>
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ describe('navigation', () => {
|
|||
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
|
||||
const mockRegistrationAddress = {
|
||||
streetAddress: '1234 Main St',
|
||||
|
|
@ -135,14 +135,6 @@ describe('navigation', () => {
|
|||
state: 'OH',
|
||||
zipCode: '43215'
|
||||
};
|
||||
const mockvehicles = [
|
||||
{
|
||||
vin: 'TEST_VIN'
|
||||
},
|
||||
{
|
||||
vin: 'TEST_VIN2'
|
||||
}
|
||||
];
|
||||
|
||||
const { wrapper } = setupMocks();
|
||||
|
||||
|
|
@ -152,13 +144,12 @@ describe('navigation', () => {
|
|||
customerQuestions: {
|
||||
addressQuestions: mockRegistrationAddress
|
||||
},
|
||||
isCoverageEnabled: true,
|
||||
vehiclesCount: 2,
|
||||
vehiclesFound: mockvehicles
|
||||
isCoverageEnabled: true
|
||||
});
|
||||
|
||||
useMainStore().order.policy.policyNumber = 'p_0001';
|
||||
useMainStore().order.policy.dateOfLoss = '2022-01-28';
|
||||
useMainStore().order.vehicle.policyVehicleId = 0;
|
||||
|
||||
// Act
|
||||
|
||||
|
|
@ -167,10 +158,111 @@ describe('navigation', () => {
|
|||
// Assert
|
||||
expect(wrapper.vm.$router.navigate).toHaveBeenCalledWith(
|
||||
navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
|
||||
undefined,
|
||||
{},
|
||||
{},
|
||||
mockvehicles
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
id="sub-header"
|
||||
cmsWidgetName="SiteSubHeaderWidget"
|
||||
class="mt-4" />
|
||||
<div class="row mb-4">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
ref="policyHolderFirstName"
|
||||
|
|
@ -36,8 +36,8 @@
|
|||
:validationRules="rules.lastName" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<textboxQuestion
|
||||
ref="phoneNumber"
|
||||
v-model="customerQuestions.phoneNumber"
|
||||
|
|
@ -48,13 +48,14 @@
|
|||
:mask="phoneMask"
|
||||
disableAutoFill />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-lg-4">
|
||||
<textboxQuestion
|
||||
ref="extension"
|
||||
v-model="customerQuestions.extension"
|
||||
inputId="extensionField"
|
||||
cmsWidgetName="ExtensionQuestion"
|
||||
:validationRules="rules.extension"
|
||||
maxLength="5"
|
||||
disableAutoFill />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -65,8 +66,7 @@
|
|||
cmsWidgetName="EmailAddressQuestion"
|
||||
:validationRules="rules.email"
|
||||
isRequired
|
||||
disableAutoFill
|
||||
class="mb-4" />
|
||||
disableAutoFill />
|
||||
<addressQuestions
|
||||
id="address-questions-wrapper"
|
||||
ref="addressQuestions"
|
||||
|
|
@ -78,7 +78,7 @@
|
|||
cmsWidgetName="SiteFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
@backClicked="navigateBack" />
|
||||
@backClicked="backButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -138,7 +138,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
customerQuestions: this.getPolicyHolderDetailsFromStore(),
|
||||
vehiclesFound: [],
|
||||
rules: {
|
||||
firstName: globalRules.POLICYHOLDER_FIRST_NAME_REQUIRED,
|
||||
lastName: globalRules.POLICYHOLDER_LAST_NAME_REQUIRED,
|
||||
|
|
@ -152,8 +151,8 @@ export default {
|
|||
isCoverageEnabled() {
|
||||
return this.mainStore.issConfig.isCoverageEnabled;
|
||||
},
|
||||
vehiclesCount() {
|
||||
return this.vehiclesFound.length;
|
||||
isPolicyVehicleSelected() {
|
||||
return (this.mainStore.order.vehicle.policyVehicleId != null && this.mainStore.order.vehicle.policyVehicleId >= 0);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -163,13 +162,10 @@ export default {
|
|||
},
|
||||
|
||||
navigateForward() {
|
||||
if (this.vehiclesCount > 0) {
|
||||
if (this.isPolicyVehicleSelected) {
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED,
|
||||
this.$route,
|
||||
{},
|
||||
{},
|
||||
this.vehiclesFound
|
||||
this.$route
|
||||
);
|
||||
} else {
|
||||
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() {
|
||||
return {
|
||||
addressQuestions: {
|
||||
|
|
@ -194,7 +204,7 @@ export default {
|
|||
lastName: this.mainStore.order.customer.lastName,
|
||||
phoneNumber: this.mainStore.order.contactInfo.homePhone,
|
||||
extension: this.mainStore.order.contactInfo.extension,
|
||||
email: this.mainStore.order.customer.emailAddress,
|
||||
email: this.mainStore.order.customer.emailAddress
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -218,4 +228,10 @@ export default {
|
|||
#address-questions-wrapper .form-test-error {
|
||||
line-height: 24px;
|
||||
}
|
||||
.textbox-question {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.dropdown-question {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<buttonQuestion
|
||||
ref="policyVehiclesQuestion"
|
||||
v-model="selectedVehicleVin"
|
||||
groupName="policyVehiclesQuestionOption"
|
||||
buttonTypeString="listButton"
|
||||
isOverflowScrollable
|
||||
:answers="answers"
|
||||
isRequired
|
||||
:validationRules="validationRules" />
|
||||
ref="policyVehiclesQuestion"
|
||||
v-model="selectedVehicleVin"
|
||||
groupName="policyVehiclesQuestionOption"
|
||||
buttonTypeString="listButton"
|
||||
isOverflowScrollable
|
||||
:answers="answers"
|
||||
isRequired
|
||||
:validationRules="validationRules" />
|
||||
</template>
|
||||
<script>
|
||||
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
cmsWidgetName="PolicyVehiclesQuestion"
|
||||
:vehicles="VehiclesForQuestions"
|
||||
:validationRules="rules.optionRequired"
|
||||
class="mb-2"/>
|
||||
class="mb-2" />
|
||||
<buttonMain
|
||||
:variant="buttonVariants.primary"
|
||||
buttonText="Add another vehicle"
|
||||
|
|
@ -70,7 +70,7 @@ import coverageType from '@/constants/coverage-type';
|
|||
import alert from '@/ux-components/alert/alert.vue';
|
||||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.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 {
|
||||
name: 'policy-vehicles',
|
||||
|
|
@ -114,7 +114,7 @@ export default {
|
|||
const vehicles = this.policyVehicles;
|
||||
const mappedData =
|
||||
vehicles?.map((v) => {
|
||||
const maskSymbol = 'X';
|
||||
const maskSymbol = '*';
|
||||
const vinStart = maskSymbol.repeat(v.vin.length - 6);
|
||||
const vinEnd = v.vin.substring(v.vin.length - 6);
|
||||
return {
|
||||
|
|
@ -122,7 +122,7 @@ export default {
|
|||
vehicle: v,
|
||||
Text: `${v.vehicleYear} ${v.vehicleMake} ${v.vehicleModel}`,
|
||||
Name: v.vin,
|
||||
SubText: `VIN ${vinStart}${vinEnd}`
|
||||
SubText: `VIN: ${vinStart}${vinEnd}`
|
||||
};
|
||||
}) ?? [];
|
||||
return mappedData;
|
||||
|
|
@ -297,25 +297,24 @@ export default {
|
|||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.iss-heritage-container-width {
|
||||
.policy-vehicles-container {
|
||||
position: relative;
|
||||
min-height: 1px;
|
||||
padding-left: .9375rem;
|
||||
padding-right: .9375rem;
|
||||
}
|
||||
}
|
||||
|
||||
.policy-vehicles {
|
||||
.button-question {
|
||||
.question-text {
|
||||
margin: 1rem 0;
|
||||
span {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
:deep(.subheader-secondary p) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:deep(span.small) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:deep(.form-test-error) {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { createTestingPinia } from '@pinia/testing';
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||
import { useMainStore } from '@/store/index.js';
|
||||
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
|
||||
|
||||
// Mock fetchCmsContentForPage
|
||||
jest.mock('@/helpers/cms-content-helper', () => ({
|
||||
|
|
@ -214,7 +213,7 @@ describe('schedule-page.vue', () => {
|
|||
// Act
|
||||
const newShopTimeSlots = await wrapper.vm.getAvailableDatesMethod(
|
||||
'2023-01-01',
|
||||
'2023-01-31'
|
||||
'2023-01-15'
|
||||
);
|
||||
|
||||
// Assert
|
||||
|
|
@ -236,7 +235,7 @@ describe('schedule-page.vue', () => {
|
|||
estimatedServiceMinutesMaximum: 120
|
||||
});
|
||||
});
|
||||
test('Should call API service in days of 34 or less when getAvailableDatesMethod is called with large date ranges', async () => {
|
||||
test('Should call API service in days of 15 or less when getAvailableDatesMethod is called with large date ranges', async () => {
|
||||
// Arrange
|
||||
const { wrapper } = getShallowMountedComponent();
|
||||
wrapper.vm.selectableDatesData = {
|
||||
|
|
@ -264,7 +263,7 @@ describe('schedule-page.vue', () => {
|
|||
// 2023-01-01 --> 2023-02-05
|
||||
// 2023-02-06 --> 2023-03-12
|
||||
// 2023-03-13 --> 2023-03-31
|
||||
expect(store.getShopTimeSlots).toHaveBeenCalledTimes(3);
|
||||
expect(store.getShopTimeSlots).toHaveBeenCalledTimes(6);
|
||||
});
|
||||
});
|
||||
describe('Rendering', () => {
|
||||
|
|
@ -290,32 +289,16 @@ describe('schedule-page.vue', () => {
|
|||
// Assert
|
||||
expect(testValue).toStrictEqual('01234');
|
||||
});
|
||||
test('getDisplayTextForMilitaryTime should return the correctly formatted string', () => {
|
||||
// Arrange
|
||||
const { wrapper } = getShallowMountedComponent();
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: []
|
||||
};
|
||||
const timeInput1 = '15:00';
|
||||
const timeInput2 = '15:30';
|
||||
|
||||
// Act
|
||||
const testOutput1 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput1);
|
||||
const testOutput2 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput2);
|
||||
const testOutput3 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput1, true);
|
||||
const testOutput4 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput2, true);
|
||||
|
||||
// Assert
|
||||
expect(testOutput1).toBe('3:00 PM');
|
||||
expect(testOutput2).toBe('3:30 PM');
|
||||
expect(testOutput3).toBe('3 PM');
|
||||
expect(testOutput4).toBe('3:30 PM');
|
||||
});
|
||||
});
|
||||
test('forwardButtonAction should call route method navigateWithoutSaving', async () => {
|
||||
// Arrange
|
||||
const { wrapper } = getShallowMountedComponent();
|
||||
wrapper.vm.$router.navigate = jest.fn(() => ({}));
|
||||
wrapper.vm.selectedTimeSlotInfo = {
|
||||
timeSlot: {
|
||||
routeCode: 'test-id'
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<Form
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
@submit="onSubmit"
|
||||
@invalidSubmit="onInvalidSubmit">
|
||||
<div class="fade-on-route-transition">
|
||||
|
|
@ -16,56 +15,27 @@
|
|||
cmsWidgetName="ScheduleSubHeaderWidget"
|
||||
secondaryTextClasses="text-center small sub-text"
|
||||
class="mt-4" />
|
||||
<template v-if="ChangeShopLink.length">
|
||||
<textBlock
|
||||
cmsWidgetName="ChangeShopLink"
|
||||
justifyText="center"
|
||||
class="mb-5 text-link-small change-shop-link"
|
||||
:marginTopSizeOverride="1" />
|
||||
</template>
|
||||
<div class="main-content-container">
|
||||
<locationAlerts
|
||||
ref="locationAlerts"
|
||||
cmsWidgetPrefix="LocationAlert-" />
|
||||
<datePicker
|
||||
ref="datePicker"
|
||||
v-model="selectedDate"
|
||||
v-model="selectedTimeSlotInfo"
|
||||
customComponentId="dateQuestion"
|
||||
selectableDatesSetting="custom"
|
||||
class="text-link-small"
|
||||
:showTimeSlotError="showDatePickerError"
|
||||
:customSelectableDatesCallback="
|
||||
getAvailableDatesMethod
|
||||
"
|
||||
validationRules="date-required"
|
||||
@dateClicked="openInshopTimeSlotsModal" />
|
||||
<timeSlotModalQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
v-model="selectedTimeSlotInfo"
|
||||
customComponentId="timeSlotModalQuestion"
|
||||
cmsWidgetName="TimeSlotModalQuestion"
|
||||
mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal"
|
||||
mobileCmsWidgetName="MobileTimeSlotModal"
|
||||
dropoffCmsWidgetName="DropOffTimeSlotModal"
|
||||
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
|
||||
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
|
||||
:selectedDate="selectedDate"
|
||||
:appointmentType="appointmentType"
|
||||
:premiumAppointmentFee="mobilePremiumAppointmentFee"
|
||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||
:estimatedServiceMinutesMinimum="
|
||||
selectableDatesData.estimatedServiceMinutesMinimum
|
||||
"
|
||||
:estimatedServiceMinutesMaximum="
|
||||
selectableDatesData.estimatedServiceMinutesMaximum
|
||||
"
|
||||
validationRules="time-slot-selection-required"
|
||||
@timeSlotModalClosed="timeSlotModalClosed"
|
||||
@timeSlotSelected="forwardButtonAction" />
|
||||
@dateSelected="dateSelectedFromPicker"
|
||||
@timeSlotSelected="timeSlotSelectedFromPicker" />
|
||||
<siteFooter
|
||||
ref="navbar"
|
||||
class="mt-5"
|
||||
cmsWidgetName="SiteFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isForwardButtonNavigationDisabled="!isFormValid"
|
||||
@backClicked="navigateBack"
|
||||
@forwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
|
|
@ -80,9 +50,7 @@ import siteHeader from '@/iss-components/site-header/site-header.vue';
|
|||
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
|
||||
import locationAlerts from '@/layouts/schedule-page/location-alerts/location-alerts.vue';
|
||||
import datePicker from '@/digital-components/date-picker/date-picker.vue';
|
||||
import timeSlotModalQuestion from '@/layouts/schedule-page/time-slot-modal-question/time-slot-modal-question.vue';
|
||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||
import textBlock from '@/digital-components/text-block/text-block.vue';
|
||||
|
||||
// Supporting files
|
||||
import {
|
||||
|
|
@ -97,27 +65,15 @@ import {
|
|||
} from '@/helpers/cms-content-helper';
|
||||
import {
|
||||
calcDaysBetweenDates,
|
||||
convertDateStringToDate,
|
||||
sumDateString
|
||||
} from '@/helpers/date-helper';
|
||||
import settleAllPromises from '@/helpers/layout-helper';
|
||||
import { Form, defineRule } from 'vee-validate';
|
||||
import { Form } from 'vee-validate';
|
||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||
import errorMessages from '@/constants/error-messages';
|
||||
import { required } from '@/helpers/validation-rules';
|
||||
import { useMainStore } from '@/store';
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule('date-required', required(errorMessages.DATE_REQUIRED));
|
||||
defineRule('time-slot-selection-required', (value) => {
|
||||
if (value?.timeSlot?.routeCode == null) {
|
||||
return errorMessages.DATE_REQUIRED;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Define constants
|
||||
const TIME_SLOTS_CALL_DAYS_LIMIT = 34; // needs to be 34 for API limits (35 does not consistently work)
|
||||
const TIME_SLOTS_CALL_DAYS_LIMIT = 15;
|
||||
|
||||
const getAvailableDates = async (
|
||||
startDateString,
|
||||
|
|
@ -127,13 +83,11 @@ const getAvailableDates = async (
|
|||
) => {
|
||||
const apiEndDateLimit = sumDateString(
|
||||
startDateString,
|
||||
TIME_SLOTS_CALL_DAYS_LIMIT
|
||||
TIME_SLOTS_CALL_DAYS_LIMIT - 1
|
||||
);
|
||||
const difference = calcDaysBetweenDates(startDateString, endDateString);
|
||||
const apiCallsCount = Math.ceil(difference / TIME_SLOTS_CALL_DAYS_LIMIT);
|
||||
const storeActionConfigs = [];
|
||||
const timeSlotsData = {};
|
||||
timeSlotsData.days = [];
|
||||
let apiStartDate = startDateString;
|
||||
let apiEndDate = endDateString;
|
||||
|
||||
|
|
@ -144,7 +98,7 @@ const getAvailableDates = async (
|
|||
apiStartDate = sumDateString(apiEndDate, 1);
|
||||
apiEndDate = sumDateString(
|
||||
apiStartDate,
|
||||
TIME_SLOTS_CALL_DAYS_LIMIT
|
||||
TIME_SLOTS_CALL_DAYS_LIMIT - 1
|
||||
);
|
||||
|
||||
if (i === apiCallsCount) {
|
||||
|
|
@ -154,11 +108,7 @@ const getAvailableDates = async (
|
|||
apiEndDate = apiEndDateLimit;
|
||||
}
|
||||
|
||||
if (
|
||||
appointmentType === AppointmentTypeStrings.MOBILE
|
||||
|| appointmentType
|
||||
=== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
|
||||
) {
|
||||
if (appointmentType === AppointmentTypeStrings.MOBILE || appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
|
||||
storeActionConfig = {
|
||||
storeAction: GET_MOBILE_TIME_SLOTS,
|
||||
payload: {
|
||||
|
|
@ -232,9 +182,7 @@ export default {
|
|||
siteSubHeader,
|
||||
locationAlerts,
|
||||
datePicker,
|
||||
timeSlotModalQuestion,
|
||||
siteFooter,
|
||||
textBlock,
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Form
|
||||
},
|
||||
|
|
@ -300,7 +248,6 @@ export default {
|
|||
resultMap.datePickerInitialData.initialShopTimeSlotsResponse,
|
||||
resultMap.premiumFeeWithPrice
|
||||
);
|
||||
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
|
||||
});
|
||||
},
|
||||
setup() {
|
||||
|
|
@ -312,6 +259,7 @@ export default {
|
|||
selectedDate: this.getSelectedDate(),
|
||||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||
selectableDatesData: [],
|
||||
showDatePickerError: false,
|
||||
mobilePremiumAppointmentFee: null
|
||||
};
|
||||
},
|
||||
|
|
@ -325,38 +273,14 @@ export default {
|
|||
appointmentType() {
|
||||
return useMainStore().order.serviceLocation.appointmentType;
|
||||
},
|
||||
timeSlotsForSelectedDate() {
|
||||
if (!this.selectedDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.selectableDatesData.days?.find((selectableDate) => selectableDate.date === this.selectedDate);
|
||||
isFormValid() {
|
||||
const hasTimeSlotSelected = this.selectedTimeSlotInfo?.timeSlot?.routeCode != null;
|
||||
return hasTimeSlotSelected;
|
||||
},
|
||||
supportingItems() {
|
||||
return useMainStore().lineItems.supportingItems;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedDate(newValue, oldValue) {
|
||||
// Clear time slot selection if date selected changes
|
||||
if (newValue !== oldValue) {
|
||||
this.selectedTimeSlotInfo = {
|
||||
timeSlot: {
|
||||
date: null,
|
||||
routeCode: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null
|
||||
},
|
||||
isPremiumAppointment: null
|
||||
};
|
||||
}
|
||||
},
|
||||
selectedTimeSlotInfo(newValue) {
|
||||
this.updateFooterButtonText(newValue);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
arePagePrerequisitesValid() {
|
||||
|
|
@ -400,9 +324,6 @@ export default {
|
|||
getServiceZipCtuCodeFromStore() {
|
||||
return this.mainStore.order.serviceLocation.zipCodeCtu;
|
||||
},
|
||||
openInshopTimeSlotsModal() {
|
||||
this.$refs.timeSlotModalQuestion.openModal();
|
||||
},
|
||||
getSelectedDate() {
|
||||
return this.mainStore.order.schedule.date;
|
||||
},
|
||||
|
|
@ -420,64 +341,21 @@ export default {
|
|||
|
||||
return selectedTimeSlotInfo;
|
||||
},
|
||||
timeSlotModalClosed() {
|
||||
// Clear the selectedDate if no timeSlot has been selected
|
||||
if (this.selectedTimeSlotInfo.timeSlot.routeCode == null) {
|
||||
this.selectedDate = null;
|
||||
}
|
||||
dateSelectedFromPicker(date) {
|
||||
this.selectedDate = date;
|
||||
this.showDatePickerError = false;
|
||||
},
|
||||
updateFooterButtonText(timeSlotInfo) {
|
||||
let navbarButtonText;
|
||||
if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) {
|
||||
navbarButtonText = 'Continue';
|
||||
} else {
|
||||
navbarButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(timeSlotInfo.timeSlot.date)}`;
|
||||
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(timeSlotInfo.timeSlot.startTime)}`;
|
||||
} else if (
|
||||
this.appointmentType === AppointmentTypeStrings.MOBILE
|
||||
&& !timeSlotInfo.isPremiumAppointment
|
||||
) {
|
||||
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlotInfo.timeSlot.startTime,
|
||||
true
|
||||
)} - ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlotInfo.timeSlot.endTime,
|
||||
true
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
this.$refs.navbar.updateButtonText(navbarButtonText);
|
||||
},
|
||||
convertSelectedDateToShortMonthAndDay(selectedDate) {
|
||||
// This conversion ensures we don't get get GMT induced date changes
|
||||
const dateObject = convertDateStringToDate(selectedDate);
|
||||
return dateObject.toLocaleDateString('en-us', {
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
},
|
||||
getDisplayTextForMilitaryTime(
|
||||
militaryTimeInput,
|
||||
shouldTrimMinutesIfEmpty = false
|
||||
) {
|
||||
// Expected input: "HH:MM"
|
||||
let hours = parseInt(militaryTimeInput.split(':')[0], 10);
|
||||
const minutes = militaryTimeInput.split(':')[1];
|
||||
const meridianNotation = hours > 11 ? 'PM' : 'AM';
|
||||
|
||||
if (hours > 12) {
|
||||
hours -= 12;
|
||||
}
|
||||
|
||||
if (shouldTrimMinutesIfEmpty && minutes === '00') {
|
||||
return `${hours} ${meridianNotation}`;
|
||||
}
|
||||
return `${hours}:${minutes} ${meridianNotation}`;
|
||||
timeSlotSelectedFromPicker(timeSlot) {
|
||||
this.selectedTimeSlotInfo = timeSlot;
|
||||
this.showDatePickerError = false;
|
||||
},
|
||||
forwardButtonAction() {
|
||||
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
|
||||
if (!this.isFormValid) {
|
||||
this.showDatePickerError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_FORWARD,
|
||||
this.$route
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
<template>
|
||||
<transition
|
||||
name="fade"
|
||||
mode="out-in">
|
||||
<div
|
||||
v-if="isDisplayed"
|
||||
class="appointment-type-question"
|
||||
aria-live="polite">
|
||||
<buttonQuestion
|
||||
ref="buttonQuestion"
|
||||
v-model="selectedValues"
|
||||
customButtonQuestionId="appointmentTypeQuestion"
|
||||
:questionText="questionText"
|
||||
:answers="answersToDisplay"
|
||||
:groupName="groupName"
|
||||
buttonTypeString="listCard"
|
||||
:suppressError="suppressError"
|
||||
:validationRules="validationRules"
|
||||
isRequired />
|
||||
</div>
|
||||
</transition>
|
||||
<transition
|
||||
name="fade"
|
||||
mode="out-in">
|
||||
<div
|
||||
class="appointment-type-question"
|
||||
aria-live="polite">
|
||||
<buttonQuestion
|
||||
ref="buttonQuestion"
|
||||
v-model="selectedValues"
|
||||
customButtonQuestionId="appointmentTypeQuestion"
|
||||
:answers="answersToDisplay"
|
||||
:groupName="groupName"
|
||||
:suppressError="suppressError"
|
||||
:validationRules="validationRules"
|
||||
isRequired />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -38,12 +35,9 @@ export default {
|
|||
modelValue: String,
|
||||
groupName: String,
|
||||
isAvailable: Boolean,
|
||||
isDisplayed: Boolean,
|
||||
suppressError: Boolean,
|
||||
validationRules: String,
|
||||
cmsWidgetName: String,
|
||||
isServiceableMobile: Boolean,
|
||||
isServiceableInshop: Boolean
|
||||
cmsWidgetName: String
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
computed: {
|
||||
|
|
@ -51,23 +45,13 @@ export default {
|
|||
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
||||
},
|
||||
answersFromCms() {
|
||||
const retCms = 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;
|
||||
return this.getCmsContent(this.cmsWidgetName, 'Answers');
|
||||
},
|
||||
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
|
||||
? this.answersFromCms.filter((answer) => (
|
||||
(answer.Name === AppointmentTypeStrings.IN_SHOP && shouldShowInshop)
|
||||
|| (answer.Name === AppointmentTypeStrings.MOBILE && shouldShowMobile)
|
||||
|| (answer.Name === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP && shouldShowMobileNotITACNotNoComp)
|
||||
|| (answer.Name === AppointmentTypeStrings.DROP_OFF && shouldShowDropoff)
|
||||
(answer.Name === AppointmentTypeStrings.IN_SHOP)
|
||||
|| (answer.Name === AppointmentTypeStrings.MOBILE)
|
||||
))
|
||||
: [];
|
||||
},
|
||||
|
|
@ -78,58 +62,13 @@ export default {
|
|||
set(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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.button-question) {
|
||||
.list-card img {
|
||||
height: auto;
|
||||
width: 3.417rem;
|
||||
}
|
||||
.appointment-type-question {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,13 @@
|
|||
/* eslint-env jest */
|
||||
import baseMixin from '@/mixins/base-mixin';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||
import { mount, flushPromises } from '@vue/test-utils';
|
||||
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';
|
||||
|
||||
// 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 };
|
||||
}
|
||||
import { getZipCodeData } from '@/helpers/service-location-helper';
|
||||
|
||||
const mockGetServiceabilityDetails = () => {
|
||||
const serviceabilityDetails = {
|
||||
|
|
@ -35,123 +19,199 @@ const mockGetServiceabilityDetails = () => {
|
|||
|
||||
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(
|
||||
'@/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 = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
// linkWidgetName is not defined. This whole spec file needs review.
|
||||
if (widgetName === linkWidgetName) {
|
||||
return mockLinkCmsContent[cmsFieldName];
|
||||
const mockRoute = {
|
||||
params: {}
|
||||
};
|
||||
const mockRouter = {
|
||||
navigate: jest.fn()
|
||||
};
|
||||
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) {
|
||||
return mockModalCmsContent[cmsFieldName];
|
||||
}
|
||||
|
||||
return null;
|
||||
}),
|
||||
|
||||
getZipCodeData: jest.fn((zip) => {
|
||||
if (zip === '43235' || zip === '55555') {
|
||||
return Promise.resolve({
|
||||
containsMilitaryBase: false,
|
||||
isValid: true,
|
||||
state: 'OH',
|
||||
zipCodeCtu: '01820'
|
||||
});
|
||||
}
|
||||
|
||||
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()
|
||||
],
|
||||
mocks: {
|
||||
$route: mockRoute,
|
||||
$router: mockRouter
|
||||
},
|
||||
stubs: {
|
||||
alert: true,
|
||||
textBlock: true,
|
||||
appointmentTypeQuestion: true,
|
||||
contentGroupModal: true,
|
||||
siteFooter: true,
|
||||
siteHeader: true,
|
||||
siteSubHeader: true,
|
||||
shopAddress: true,
|
||||
serviceZipQuestion: true
|
||||
}
|
||||
}
|
||||
};
|
||||
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', () => {
|
||||
test('if the back button is clicked, navigate back', async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({
|
||||
wrapper.vm.$router.navigateWithSpinner = jest.fn();
|
||||
wrapper.vm.navigateBack = baseMixin.methods.navigateBack;
|
||||
|
||||
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
|
||||
await wrapper.vm.navigateBack();
|
||||
test('displays military zip message when zip is updated', async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({ appointmentType: AppointmentTypeStrings.MOBILE });
|
||||
|
||||
// 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({
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
const newServiceZipCodeQuestion = {
|
||||
zipCode: '61606',
|
||||
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);
|
||||
const mobileServiceZipCodeQuestion = wrapper.findComponent({
|
||||
ref: 'mobileServiceZipCodeQuestion'
|
||||
});
|
||||
|
||||
expect(wrapper.vm.zipContainsMilitaryBase).toBe(false);
|
||||
|
||||
const newMobileServiceZipCode = '45433'
|
||||
|
||||
// Act
|
||||
mobileServiceZipCodeQuestion.vm.$emit('update:modelValue', newMobileServiceZipCode);
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.zipContainsMilitaryBase).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,28 +12,7 @@
|
|||
<div class="service-location-container iss-heritage-content-container-width">
|
||||
<siteSubHeader
|
||||
cmsWidgetName="SiteSubHeaderWidget"
|
||||
class="mt-4" />
|
||||
<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" />
|
||||
class="subheader" />
|
||||
<alert
|
||||
v-if="displayRecalibrationWarning"
|
||||
ref="alertRecalNoMobile"
|
||||
|
|
@ -41,18 +20,6 @@
|
|||
cmsWidgetName="AlertRecalNoMobileWidget"
|
||||
alertClass="alert-warning"
|
||||
@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
|
||||
v-if="displayBigTruckNoShops"
|
||||
ref="alertBigTruckNoShops"
|
||||
|
|
@ -60,41 +27,94 @@
|
|||
cmsWidgetName="AlertBigTruckNoShopsWidget"
|
||||
alertClass="alert-warning" />
|
||||
<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
|
||||
v-show="isAppointmentTypeDisplayed"
|
||||
v-if="!requiresInshopRecalibration && isServiceableMobile"
|
||||
ref="appointmentTypeQuestion"
|
||||
v-model="selectedAppointmentType"
|
||||
:isServiceableMobile="isServiceableMobile"
|
||||
:isServiceableInshop="isServiceableInshop"
|
||||
:isDisplayed="isAppointmentTypeDisplayed"
|
||||
groupName="appointmentTypeQuestion"
|
||||
cmsWidgetName="AppointmentTypeQuestionWidget"
|
||||
validationRules="option-required" />
|
||||
</div>
|
||||
<mobileLocationModalQuestions
|
||||
v-if="isMobileLocationDisplayed"
|
||||
ref="mobileLocationQuestions"
|
||||
v-model="mobileLocationQuestions"
|
||||
customComponentId="mobileLocationQuestions"
|
||||
:mobileFeePart="mobileFeePart"
|
||||
validationRules="mobile-location-required"
|
||||
linkWidgetName="MobileLocationLinkWidget"
|
||||
modalWidgetName="MobileLocationModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData"
|
||||
@updated-mobile-fee-part="setMobileFeePart"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
@updated-mobile-ctu="setCtuForMobile" />
|
||||
<shopQuestion
|
||||
v-show="isShopQuestionDisplayed"
|
||||
:ref="SHOP_QUESTION_REF_NAME"
|
||||
v-model="selectedProvider"
|
||||
:selectedAppointmentType="selectedAppointmentType"
|
||||
:isDisplayed="isShopQuestionDisplayed"
|
||||
cmsWidgetName="ShopQuestionWidget"
|
||||
@updatedMobileProviderNumber="
|
||||
setMobileProviderNumber
|
||||
" />
|
||||
<div
|
||||
v-if="isInshop">
|
||||
<alert
|
||||
v-if="displayLowAvailabilityInshop"
|
||||
ref="alertLowAvailabilityInshop"
|
||||
cmsWidgetName="AlertLowAvailabilityInshopWidget"
|
||||
:isCollapsible="true"
|
||||
alertClass="alert-warning" />
|
||||
<shopAddress
|
||||
ref="shopAddress"
|
||||
v-model="selectedProvider"
|
||||
:serviceZipcode="zipCode"
|
||||
:selectedAppointmentType="selectedAppointmentType"
|
||||
modalWidgetName="ChangeLocationModalWidget"
|
||||
cmsWidgetName="ShopAddressWidget"
|
||||
@zip-updated="handleInShopZipUpdated" />
|
||||
<!-- INTEGRATION TODO: Put this right under the calendar -->
|
||||
<alert
|
||||
v-if="selectedProvider === null"
|
||||
ref="alertNoAvailableShops"
|
||||
cmsWidgetName="AlertNoAvailableShopsWidget"
|
||||
:isCollapsible="true"
|
||||
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
|
||||
:ref="RECAL_MODAL_REF_NAME"
|
||||
cssModalHeadlineClass="text-center"
|
||||
|
|
@ -104,7 +124,7 @@
|
|||
class="mt-5"
|
||||
cmsWidgetName="SiteFooterWidget"
|
||||
:isForwardActionDisabled="
|
||||
!meta.valid || displayNoShopsAlert
|
||||
!meta.valid || displayNoShopsAlert || displayBigTruckNoShops
|
||||
"
|
||||
@backClicked="navigateBack(this, navigateBackScenario)"
|
||||
@forwardClicked="forwardButtonAction" />
|
||||
|
|
@ -118,44 +138,31 @@
|
|||
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
|
||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||
import settleAllPromises from '@/helpers/layout-helper';
|
||||
import { required } from '@/helpers/validation-rules';
|
||||
import errorMessages from '@/constants/error-messages';
|
||||
import { useMainStore } from '@/store';
|
||||
import {
|
||||
getPricedMobileFeePart,
|
||||
getServiceabilityDetails,
|
||||
getZipCodeData
|
||||
getZipCodeData,
|
||||
getMobileZipCodeData
|
||||
} from '@/helpers/service-location-helper';
|
||||
import { toTitleCase } from '@/helpers/text-helper.js';
|
||||
import { getAvailabilityRating } from '@/helpers/service-location-helper';
|
||||
|
||||
// Import Component
|
||||
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 baseFormMixin from '@/mixins/base-form-mixin';
|
||||
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 shopQuestion from '@/layouts/service-location/shop-question/shop-question.vue';
|
||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||
import siteHeader from '@/iss-components/site-header/site-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 SITE_FOOTER_REF_NAME = 'siteFooter';
|
||||
|
||||
|
|
@ -163,16 +170,16 @@ export default {
|
|||
name: 'service-location',
|
||||
components: {
|
||||
alert,
|
||||
textBlock,
|
||||
appointmentTypeQuestion,
|
||||
contentGroupModal,
|
||||
mobileLocationModalQuestions,
|
||||
siteFooter,
|
||||
siteHeader,
|
||||
siteSubHeader,
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Form,
|
||||
serviceZipModalQuestion,
|
||||
shopQuestion
|
||||
shopAddress,
|
||||
serviceZipQuestion
|
||||
},
|
||||
mixins: [baseFormMixin],
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -182,8 +189,8 @@ export default {
|
|||
const zipCodeData = getZipCodeData(serviceZipCode);
|
||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
|
||||
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
|
||||
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode);
|
||||
const getGlassFeesPromise = useMainStore().getGlassFees();
|
||||
const providersPromise = useMainStore().getProviders(serviceZipCode);
|
||||
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [
|
||||
|
|
@ -203,13 +210,13 @@ export default {
|
|||
resultKey: 'serviceabilityDetails',
|
||||
promise: serviceabilityDetailsPromise
|
||||
},
|
||||
{
|
||||
resultKey: 'shopQuestionInitialData',
|
||||
promise: shopQuestionInitialDataPromise
|
||||
},
|
||||
{
|
||||
resultKey: 'zipCodeData',
|
||||
promise: zipCodeData
|
||||
},
|
||||
{
|
||||
resultKey: 'providers',
|
||||
promise: providersPromise
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -221,10 +228,9 @@ export default {
|
|||
resultMap.zipCodeData,
|
||||
resultMap.serviceabilityDetails,
|
||||
resultMap.mobileFeePart,
|
||||
resultMap.shopQuestionInitialData?.mobileProviderNumber,
|
||||
resultMap.glassFees
|
||||
resultMap.glassFees,
|
||||
resultMap.providers
|
||||
);
|
||||
vm.$refs[SHOP_QUESTION_REF_NAME].initializeComponent(resultMap.shopQuestionInitialData);
|
||||
});
|
||||
},
|
||||
setup() {
|
||||
|
|
@ -238,7 +244,6 @@ export default {
|
|||
city: this.getServiceCityFromStore(),
|
||||
state: this.getServiceStateFromStore(),
|
||||
zipCode: this.getServiceZipCodeFromStore(),
|
||||
isVehicleProtected: this.getIsVehicleProtectedFromStore(),
|
||||
isGlassServiceableInshop: null,
|
||||
isRecalibrationServiceableInshop: null,
|
||||
isGlassServiceableMobile: null,
|
||||
|
|
@ -249,9 +254,13 @@ export default {
|
|||
mobileProviderNumber: null,
|
||||
zipContainsMilitaryBase: false,
|
||||
zipCodeCtu: null,
|
||||
SHOP_QUESTION_REF_NAME,
|
||||
mobileZipCode: '',
|
||||
mobileZipError: '',
|
||||
militaryBaseWarningExpanded: false,
|
||||
availabilityRating: null,
|
||||
RECAL_MODAL_REF_NAME,
|
||||
SITE_FOOTER_REF_NAME
|
||||
SITE_FOOTER_REF_NAME,
|
||||
errorMessages
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -264,61 +273,6 @@ export default {
|
|||
answersFromCms() {
|
||||
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() {
|
||||
if (this.isRecalibrationServiceableMobile !== null) {
|
||||
return (
|
||||
|
|
@ -338,22 +292,14 @@ export default {
|
|||
|
||||
return this.isGlassServiceableInshop;
|
||||
},
|
||||
isShopQuestionDisplayed() {
|
||||
isInshop() {
|
||||
return (
|
||||
this.selectedAppointmentType === 'Inshop'
|
||||
|| this.selectedAppointmentType === 'Dropoff'
|
||||
);
|
||||
},
|
||||
isAppointmentTypeDisplayed() {
|
||||
return this.zipCode && !this.displayNoShopsAlert;
|
||||
},
|
||||
isMobileLocationDisplayed() {
|
||||
return (
|
||||
this.selectedAppointmentType
|
||||
=== AppointmentTypeStrings.MOBILE
|
||||
|| this.selectedAppointmentType
|
||||
=== AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP
|
||||
);
|
||||
isMobile() {
|
||||
return this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|
||||
},
|
||||
requiresInshopRecalibration() {
|
||||
// Specifically check for isRecalibrationServiceableMobile === false, not null or true.
|
||||
|
|
@ -375,15 +321,17 @@ export default {
|
|||
displayRecalibrationWarning() {
|
||||
return this.requiresInshopRecalibration;
|
||||
},
|
||||
displayLowAvailabilityInshop() {
|
||||
return this.availabilityRating === 'low' && this.isInshop;
|
||||
},
|
||||
displayServiceableInshopOnly() {
|
||||
return (
|
||||
!this.displayRecalibrationWarning
|
||||
&& this.isServiceableInshop
|
||||
&& !this.isServiceableMobile
|
||||
);
|
||||
return !this.isServiceableMobile && this.isServiceableInshop && !this.displayRecalibrationWarning;
|
||||
},
|
||||
displayServiceableMobileOnly() {
|
||||
return this.isServiceableMobile && !this.isServiceableInshop;
|
||||
return this.isServiceableMobile && this.recalibrationRequired && !this.isServiceableInshop;
|
||||
},
|
||||
displayMobileFeeDisclaimer() {
|
||||
return this.mainStore.isNoComp || this.mainStore.isITAC;
|
||||
},
|
||||
navigateBackScenario() {
|
||||
const { isNoComp, isITAC } = useMainStore();
|
||||
|
|
@ -393,6 +341,38 @@ export default {
|
|||
},
|
||||
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: {
|
||||
|
|
@ -401,13 +381,10 @@ export default {
|
|||
useMainStore().order.serviceLocation.zipCode !== null
|
||||
);
|
||||
},
|
||||
async reloadShopData(zipCode) {
|
||||
await this.$refs[SHOP_QUESTION_REF_NAME].reloadShopData(zipCode);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
let provider = this.selectedProvider;
|
||||
this.mainStore.updateMobileFee(null);
|
||||
if (this.isMobileLocationDisplayed) {
|
||||
if (this.isMobile) {
|
||||
if (this.mainStore.isNoComp || this.mainStore.isITAC) {
|
||||
this.mainStore.updateMobileFee(this.mobileFeePart);
|
||||
}
|
||||
|
|
@ -432,7 +409,6 @@ export default {
|
|||
zipCode: this.zipCode,
|
||||
zipCodeCtu: this.zipCodeCtu,
|
||||
appointmentType: this.selectedAppointmentType,
|
||||
isVehicleProtected: this.isVehicleProtected,
|
||||
provider
|
||||
});
|
||||
|
||||
|
|
@ -466,9 +442,6 @@ export default {
|
|||
|| useMainStore().order.customer.address.zipCode
|
||||
);
|
||||
},
|
||||
getIsVehicleProtectedFromStore() {
|
||||
return useMainStore().order.serviceLocation.isVehicleProtected;
|
||||
},
|
||||
getSelectedAppointmentType() {
|
||||
return useMainStore().order.serviceLocation.appointmentType;
|
||||
},
|
||||
|
|
@ -482,12 +455,13 @@ export default {
|
|||
zipCodeData,
|
||||
serviceabilityDetails,
|
||||
mobileFeePart,
|
||||
mobileProviderNumber,
|
||||
glassFees
|
||||
glassFees,
|
||||
providers
|
||||
) {
|
||||
if (zipCodeData) {
|
||||
this.zipContainsMilitaryBase = zipCodeData.containsMilitaryBase;
|
||||
this.zipCodeCtu = zipCodeData.zipCodeCtu;
|
||||
this.city = zipCodeData.city;
|
||||
}
|
||||
|
||||
if (serviceabilityDetails) {
|
||||
|
|
@ -498,8 +472,24 @@ export default {
|
|||
this.mobileFeePart = mobileFeePart;
|
||||
}
|
||||
|
||||
if (mobileProviderNumber) {
|
||||
this.setMobileProviderNumber(mobileProviderNumber);
|
||||
if (providers) {
|
||||
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) {
|
||||
|
|
@ -521,13 +511,6 @@ export default {
|
|||
setMobileProviderNumber(providerNumber) {
|
||||
this.mobileProviderNumber = providerNumber;
|
||||
},
|
||||
resetMobileLocation() {
|
||||
this.streetAddress = '';
|
||||
this.streetAddress2 = '';
|
||||
this.city = '';
|
||||
|
||||
this.isVehicleProtected = null;
|
||||
},
|
||||
setServiceabilityDetails(serviceabilityDetails) {
|
||||
this.isGlassServiceableInshop =
|
||||
serviceabilityDetails.isGlassServiceableInshop;
|
||||
|
|
@ -537,14 +520,94 @@ export default {
|
|||
serviceabilityDetails.isGlassServiceableMobile;
|
||||
this.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>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
$page-side-padding: 1.5rem;
|
||||
|
||||
.iss-heritage-container-width {
|
||||
.service-location-container {
|
||||
position: relative;
|
||||
|
|
@ -554,58 +617,41 @@ $page-side-padding: 1.5rem;
|
|||
}
|
||||
}
|
||||
|
||||
.question-text {
|
||||
span {
|
||||
line-height: 1.5rem;
|
||||
text-align: center;
|
||||
.service-location-container {
|
||||
.subheader {
|
||||
margin: 1.25rem 0;
|
||||
}
|
||||
}
|
||||
.list-card-content p {
|
||||
&:first-of-type {
|
||||
line-height: 1.5rem;
|
||||
.form-test-error {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
&:not(:nth-of-type(1)) {
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
}
|
||||
.choose-option {
|
||||
.button-question > div {
|
||||
&:first-of-type {
|
||||
margin-bottom: 0.95rem;
|
||||
line-height: 1.5rem;
|
||||
.appointment-type-question-text {
|
||||
color: $black;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.625rem;
|
||||
flex-direction: column;
|
||||
.recal-text {
|
||||
margin-bottom: 1.25rem;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
.button-question {
|
||||
.row.form-test-error {
|
||||
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;
|
||||
}
|
||||
.mobile-service-zip-question {
|
||||
:deep(.form-test-error span) {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
.question-text {
|
||||
margin: 0;
|
||||
span {
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
.expandable-link-container {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
.service-location-button-question {
|
||||
.question-text {
|
||||
margin-top: 1.5rem;
|
||||
|
||||
span {
|
||||
text-align: center;
|
||||
.expandable-link {
|
||||
font-style: italic;
|
||||
font-weight: $font-weight-bold;
|
||||
text-decoration: none;
|
||||
color: $heritage-blue-primary;
|
||||
line-height: 1.375rem;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
<script>
|
||||
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 alert from '@/ux-components/alert/alert.vue';
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
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', () => {
|
||||
it('Should get the modelValue', async () => {
|
||||
// Arrange
|
||||
const text = 'test';
|
||||
const text = '12345';
|
||||
const wrapper = shallowMount(serviceZipQuestion, {
|
||||
props: {
|
||||
modelValue: text
|
||||
|
|
@ -13,16 +13,16 @@ describe('service-zip-question.vue', () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const modelValueText = wrapper.vm.value;
|
||||
wrapper.vm.value = 'test also';
|
||||
const modelValueText = wrapper.vm.internalZipcode;
|
||||
wrapper.vm.internalZipcode = '55555';
|
||||
|
||||
// Assert
|
||||
expect(modelValueText).toEqual('test');
|
||||
expect(modelValueText).toEqual('12345');
|
||||
});
|
||||
|
||||
it('Should emit to set value', async () => {
|
||||
// Arrange
|
||||
const text = 'test';
|
||||
const text = '12345';
|
||||
const wrapper = shallowMount(serviceZipQuestion, {
|
||||
props: {
|
||||
modelValue: text
|
||||
|
|
@ -31,9 +31,10 @@ describe('service-zip-question.vue', () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.value = 'test also';
|
||||
wrapper.vm.internalZipcode = '55555';
|
||||
wrapper.vm.updateModelValue();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted('update:modelValue')).toEqual([['test also']]);
|
||||
expect(wrapper.emitted('update:modelValue')).toEqual([['55555']]);
|
||||
});
|
||||
});
|
||||
|
|
@ -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>
|
||||
411
src/layouts/service-location/shop-address/shop-address.vue
Normal file
411
src/layouts/service-location/shop-address/shop-address.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -2,24 +2,20 @@
|
|||
|
||||
exports[`tpa-submit returns the initial data 1`] = `
|
||||
Object {
|
||||
"companyName": "Frederick Jones",
|
||||
"customValueMap": Object {
|
||||
"glassShop": "Frederick Jones",
|
||||
"modalPositions": Object {
|
||||
"center": "center",
|
||||
"edge": "edge",
|
||||
},
|
||||
"sections": Array [],
|
||||
"widget": Object {
|
||||
"damageLocations": "DamageLocationsWidget",
|
||||
"alertIncomplete": "AlertIncompleteWidget",
|
||||
"alertRecalWarning": "AlertRecalWarningWidget",
|
||||
"contactDetails": "ContactDetailsSectionWidget",
|
||||
"editShopLinkText": "EditShopLinkTextWidget",
|
||||
"footer": "SiteFooterWidget",
|
||||
"orderDetails": "OrderDetailsContent",
|
||||
"serviceSummary": "ServiceSummaryContent",
|
||||
"siteHeader": "SiteHeaderWidget",
|
||||
"siteSubHeader": "SiteSubHeaderWidget",
|
||||
"subheader": Object {
|
||||
"contactInfo": "ContactDetailsSubTitle",
|
||||
"damage": "DamageSubTitle",
|
||||
"shop": "PreferredShopSubTitle",
|
||||
"vehicle": "VehicleSubTitle",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@
|
|||
exports[`contact-details-drawer snapshot matches returns the initial data 1`] = `
|
||||
Object {
|
||||
"emailAddress": "fred.tay@gmail.com",
|
||||
"extension": null,
|
||||
"firstName": "Frederick",
|
||||
"isModalOpened": false,
|
||||
"lastName": "Taylor",
|
||||
"modalPositions": Object {
|
||||
"center": "center",
|
||||
"edge": "edge",
|
||||
},
|
||||
"phoneNumber": "606-009-2943",
|
||||
"rules": Object {
|
||||
"emailAddress": "email-required|email-address-format",
|
||||
"extension": "extension-format",
|
||||
"firstName": "first-name-required",
|
||||
"lastName": "last-name-required",
|
||||
"phoneNumber": "phone-number-required|phone-number-format",
|
||||
|
|
@ -16,6 +22,7 @@ Object {
|
|||
"widget": Object {
|
||||
"drawerFooter": "ContactDetailsDrawerFooterWidget",
|
||||
"emailQuestion": "EmailQuestionWidget",
|
||||
"extensionQuestion": "ExtensionQuestionWidget",
|
||||
"firstNameQuestion": "FirstNameQuestionWidget",
|
||||
"lastNameQuestion": "LastNameQuestionWidget",
|
||||
"phoneNumberQuestion": "PhoneNumberQuestionWidget",
|
||||
|
|
|
|||
|
|
@ -65,15 +65,16 @@ describe('contact-details-drawer', () => {
|
|||
describe('method', () => {
|
||||
describe('saveContactDetails', () => {
|
||||
test.each([
|
||||
['Sarah', 'Jones', 's.jones@gmail.com', '724-996-0909'],
|
||||
[null, 'Jones', 's.jones@gmail.com', '724-996-0909'],
|
||||
['Sarah', null, 's.jones@gmail.com', '724-996-0909'],
|
||||
['Sarah', 'Jones', null, '724-996-0909'],
|
||||
['Sarah', 'Jones', 's.jones@gmail.com', null]
|
||||
['Sarah', 'Jones', 's.jones@gmail.com', '724-996-0909', '12345'],
|
||||
[null, 'Jones', 's.jones@gmail.com', '724-996-0909', '12345'],
|
||||
['Sarah', null, 's.jones@gmail.com', '724-996-0909', '12345'],
|
||||
['Sarah', 'Jones', null, '724-996-0909', '12345'],
|
||||
['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',
|
||||
(firstName, lastName, emailAddress, phoneNumber) => {
|
||||
'when first name data "%p", last name "%p", email "%p", phone number "%p", and extension "%p", updateContactInfo called with expected',
|
||||
(firstName, lastName, emailAddress, phoneNumber, extension) => {
|
||||
// Arrange
|
||||
const mainInitialState = {
|
||||
order: {
|
||||
|
|
@ -81,12 +82,13 @@ describe('contact-details-drawer', () => {
|
|||
firstName: 'Frederick',
|
||||
lastName: 'Taylor',
|
||||
emailAddress: 'fred.tay@gmail.com',
|
||||
servicePhone: '606-009-2943'
|
||||
servicePhone: '606-009-2943',
|
||||
extension: '11111'
|
||||
}
|
||||
}
|
||||
};
|
||||
const initialData = {
|
||||
firstName, lastName, emailAddress, phoneNumber
|
||||
firstName, lastName, emailAddress, phoneNumber, extension
|
||||
};
|
||||
const { wrapper } = getMountedComponent(mainInitialState, initialData);
|
||||
|
||||
|
|
@ -98,7 +100,7 @@ describe('contact-details-drawer', () => {
|
|||
expect(useMainStore().updateContactInfo).toBeCalledTimes(1);
|
||||
expect(useMainStore().updateContactInfo).toBeCalledWith({ firstName, lastName, emailAddress });
|
||||
expect(useMainStore().updatePhoneNumbers).toBeCalledTimes(1);
|
||||
expect(useMainStore().updatePhoneNumbers).toBeCalledWith({ home: phoneNumber, service: phoneNumber });
|
||||
expect(useMainStore().updatePhoneNumbers).toBeCalledWith({ home: phoneNumber, service: phoneNumber, extension: extension });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<modal
|
||||
:ref="modalName"
|
||||
:modalPosition="modalPositions.center"
|
||||
:headerText="headerText"
|
||||
:footerButtonText="footerButtonText"
|
||||
:onModalClosedCallback="resetFormValues"
|
||||
|
|
@ -8,25 +9,27 @@
|
|||
@isModalOpened="setModalStatus"
|
||||
@footerButtonEvent="clickFooterButtonEvent">
|
||||
<template v-if="isModalOpened">
|
||||
<textboxQuestion
|
||||
ref="firstNameQuestion"
|
||||
v-model="firstName"
|
||||
inputId="firstName"
|
||||
:cmsWidgetName="widget.firstNameQuestion"
|
||||
isRequired
|
||||
:validationRules="rules.firstName" />
|
||||
<textboxQuestion
|
||||
ref="lastNameQuestion"
|
||||
v-model="lastName"
|
||||
class="mt-4"
|
||||
inputId="lastName"
|
||||
:cmsWidgetName="widget.lastNameQuestion"
|
||||
isRequired
|
||||
:validationRules="rules.lastName" />
|
||||
<div class="row">
|
||||
<textboxQuestion
|
||||
class="col"
|
||||
ref="firstNameQuestion"
|
||||
v-model="firstName"
|
||||
inputId="firstName"
|
||||
:cmsWidgetName="widget.firstNameQuestion"
|
||||
isRequired
|
||||
:validationRules="rules.firstName" />
|
||||
<textboxQuestion
|
||||
class="col"
|
||||
ref="lastNameQuestion"
|
||||
v-model="lastName"
|
||||
inputId="lastName"
|
||||
:cmsWidgetName="widget.lastNameQuestion"
|
||||
isRequired
|
||||
:validationRules="rules.lastName" />
|
||||
</div>
|
||||
<textboxQuestion
|
||||
ref="emailQuestion"
|
||||
v-model="emailAddress"
|
||||
class="mt-4"
|
||||
inputId="emailAddress"
|
||||
:cmsWidgetName="widget.emailQuestion"
|
||||
isRequired
|
||||
|
|
@ -34,13 +37,20 @@
|
|||
<textboxQuestion
|
||||
ref="phoneNumberQuestion"
|
||||
v-model="phoneNumber"
|
||||
class="mt-4"
|
||||
inputId="phoneNumber"
|
||||
:cmsWidgetName="widget.phoneNumberQuestion"
|
||||
isRequired
|
||||
:mask="phoneMask"
|
||||
disableAutoFill
|
||||
:validationRules="rules.phoneNumber" />
|
||||
<textboxQuestion
|
||||
ref="extensionQuestion"
|
||||
v-model="extension"
|
||||
inputId="extension"
|
||||
:cmsWidgetName="widget.extensionQuestion"
|
||||
disableAutoFill
|
||||
maxLength="5"
|
||||
:validationRules="rules.extension" />
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
|
@ -51,10 +61,11 @@ import modal from '@/digital-components/modal/modal.vue';
|
|||
import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue';
|
||||
|
||||
// Supporting Files
|
||||
import { useMainStore } from '@/store/index.js';
|
||||
import { useMainStore } from '@/store';
|
||||
import globalRules from '@/constants/global-rules.js';
|
||||
import MaskaFormattedMasks from '@/constants/maska-masks';
|
||||
import widgetFields from '@/constants/cms-widget-fields.js';
|
||||
import { modalPositions } from '@/constants/component-variants';
|
||||
|
||||
export default {
|
||||
name: 'contact-details-drawer',
|
||||
|
|
@ -67,27 +78,32 @@ export default {
|
|||
const { firstName,
|
||||
lastName,
|
||||
emailAddress,
|
||||
servicePhone } = useMainStore().contactInfo;
|
||||
servicePhone,
|
||||
extension } = useMainStore().contactInfo;
|
||||
return {
|
||||
isModalOpened: false,
|
||||
firstName,
|
||||
lastName,
|
||||
emailAddress,
|
||||
phoneNumber: servicePhone,
|
||||
extension: extension,
|
||||
widget: {
|
||||
title: 'ContactDetailsDrawerHeaderWidget',
|
||||
firstNameQuestion: 'FirstNameQuestionWidget',
|
||||
lastNameQuestion: 'LastNameQuestionWidget',
|
||||
emailQuestion: 'EmailQuestionWidget',
|
||||
phoneNumberQuestion: 'PhoneNumberQuestionWidget',
|
||||
extensionQuestion: 'ExtensionQuestionWidget',
|
||||
drawerFooter: 'ContactDetailsDrawerFooterWidget'
|
||||
},
|
||||
rules: {
|
||||
firstName: globalRules.FIRST_NAME_REQUIRED,
|
||||
lastName: globalRules.LAST_NAME_REQUIRED,
|
||||
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: {
|
||||
|
|
@ -121,10 +137,11 @@ export default {
|
|||
lastName: this.lastName,
|
||||
emailAddress: this.emailAddress
|
||||
};
|
||||
useMainStore().updateContactInfo(contactInfo);
|
||||
useMainStore().updatePhoneNumbers({
|
||||
this.mainStore.updateContactInfo(contactInfo);
|
||||
this.mainStore.updatePhoneNumbers({
|
||||
home: this.phoneNumber,
|
||||
service: this.phoneNumber
|
||||
service: this.phoneNumber,
|
||||
extension: this.extension
|
||||
});
|
||||
this.$emit('update-contact-details');
|
||||
},
|
||||
|
|
@ -132,11 +149,13 @@ export default {
|
|||
const { firstName,
|
||||
lastName,
|
||||
emailAddress,
|
||||
servicePhone } = useMainStore().contactInfo;
|
||||
servicePhone,
|
||||
extension } = this.mainStore.contactInfo;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.emailAddress = emailAddress;
|
||||
this.phoneNumber = servicePhone;
|
||||
this.extension = extension;
|
||||
},
|
||||
openModal() {
|
||||
this.modal.openModal();
|
||||
|
|
@ -155,5 +174,14 @@ export default {
|
|||
font-size: $h5-font-size;
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe('deductible-box', () => {
|
|||
|
||||
it('renders the correct deductible label', () => {
|
||||
const labelElement = wrapper.find('#deductibleLabel');
|
||||
expect(labelElement.text()).toBe('Deductible');
|
||||
expect(labelElement.text()).toBe('Deductible:');
|
||||
});
|
||||
|
||||
it('has the correct id', () => {
|
||||
|
|
@ -26,12 +26,8 @@ describe('deductible-box', () => {
|
|||
});
|
||||
|
||||
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('d-flex');
|
||||
expect(wrapper.classes()).toContain('justify-content-between');
|
||||
expect(wrapper.classes()).toContain('align-items-center');
|
||||
expect(wrapper.classes()).toContain('py-2');
|
||||
});
|
||||
it.each([
|
||||
['', ''],
|
||||
|
|
|
|||
|
|
@ -1,17 +1,9 @@
|
|||
<template>
|
||||
<div
|
||||
id="deductibleBox"
|
||||
class="deductible-box d-flex justify-content-between align-items-center py-2">
|
||||
<p
|
||||
id="deductibleLabel"
|
||||
class="deductible-box__text">
|
||||
Deductible
|
||||
</p>
|
||||
<p
|
||||
id="deductibleValue"
|
||||
class="deductible-box__text">
|
||||
{{ value }}
|
||||
</p>
|
||||
class="deductible-box">
|
||||
<span id="deductibleLabel" class="deductible-label">Deductible:</span>
|
||||
<span id="deductibleValue" class="deductible-value">{{ value }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -28,12 +20,17 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.deductible-box {
|
||||
background-color: $green;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .25rem;
|
||||
}
|
||||
|
||||
.deductible-box__text {
|
||||
color: white;
|
||||
.deductible-label {
|
||||
color: $black;
|
||||
font-weight: 600;
|
||||
}
|
||||
.deductible-value {
|
||||
color: $green;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
font-size: 1.625rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ describe('ReviewBlock.vue', () => {
|
|||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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) => {
|
||||
expect(lineElements.at(index).text()).toBe(line);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
<template>
|
||||
<div class="review-block">
|
||||
<div class="d-flex justify-content-between">
|
||||
<p class="review-block__header small-strong pb-1">
|
||||
{{ 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 class="review-block__header">
|
||||
{{ customHeaderText }}
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
|
@ -36,14 +35,13 @@ export default {
|
|||
props: {
|
||||
customHeaderText: String,
|
||||
editScreenReaderTextCmsWidgetName: String,
|
||||
lines: Array
|
||||
lines: Array,
|
||||
editLinkText: {
|
||||
type: String,
|
||||
default: 'Edit'
|
||||
},
|
||||
},
|
||||
emits: ['click-edit'],
|
||||
data() {
|
||||
return {
|
||||
editLinkText: 'Edit'
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
editClicked() {
|
||||
this.$emit('click-edit');
|
||||
|
|
@ -59,12 +57,16 @@ export default {
|
|||
}
|
||||
.review-block__header {
|
||||
color: $black;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-bottom: .625rem;
|
||||
}
|
||||
.review-block__body--line {
|
||||
color: $gray-600;
|
||||
|
||||
.review-body {
|
||||
margin-bottom: .625rem
|
||||
}
|
||||
|
||||
.link {
|
||||
text-underline-offset: 1px;
|
||||
font-weight: $font-weight-normal
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,15 @@ import coverageStatuses from '@/constants/coverage-statuses';
|
|||
jest.mock('@/helpers/cms-content-helper', () => ({
|
||||
fetchCmsContentForPage: 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()
|
||||
}));
|
||||
|
||||
|
|
@ -129,11 +137,7 @@ describe('tpa-submit', () => {
|
|||
|
||||
// Assert
|
||||
expect(subHeader.exists()).toBeTruthy();
|
||||
expect(subHeader.props().justifyText).toBe('center');
|
||||
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');
|
||||
expect(subHeader.classes()).toContain('tpa-submit-title');
|
||||
});
|
||||
test('sub header body one', () => {
|
||||
// Arrange
|
||||
|
|
@ -144,34 +148,6 @@ describe('tpa-submit', () => {
|
|||
|
||||
// Assert
|
||||
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', () => {
|
||||
// Arrange
|
||||
|
|
@ -183,21 +159,6 @@ describe('tpa-submit', () => {
|
|||
// Assert
|
||||
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', () => {
|
||||
const title1 = 'Section 1';
|
||||
const title2 = 'Another Section';
|
||||
|
|
@ -253,24 +214,8 @@ describe('tpa-submit', () => {
|
|||
|
||||
// Assert
|
||||
expect(submitOrderDetailsTitle.exists()).toBeTruthy();
|
||||
expect(submitOrderDetailsTitle.props().marginTopSizeOverride).toBe(4);
|
||||
expect(submitOrderDetailsTitle.classes()).toContain('fw-bold');
|
||||
expect(submitOrderDetailsTitle.classes()).toContain('text-color--black');
|
||||
});
|
||||
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');
|
||||
expect(submitOrderDetailsTitle.classes()).toContain('order-details-title');
|
||||
});
|
||||
test('deductible box', () => {
|
||||
// Arrange
|
||||
|
|
@ -306,7 +251,7 @@ describe('tpa-submit', () => {
|
|||
});
|
||||
});
|
||||
describe('before route enter', () => {
|
||||
test('produces 4 sections', async () => {
|
||||
test('produces 2 sections', async () => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
expect(wrapper.vm.sections.length).toBe(0);
|
||||
|
|
@ -320,65 +265,13 @@ describe('tpa-submit', () => {
|
|||
);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.sections.length).toBe(4);
|
||||
});
|
||||
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);
|
||||
expect(wrapper.vm.sections.length).toBe(2);
|
||||
});
|
||||
describe('preferred shop section', () => {
|
||||
test('has three lines', async () => {
|
||||
test('has two lines', async () => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
const preferredShopSectionIndex = 2;
|
||||
const preferredShopSectionIndex = 0;
|
||||
|
||||
// Act
|
||||
await tpaSubmit.beforeRouteEnter.call(
|
||||
|
|
@ -390,15 +283,15 @@ describe('tpa-submit', () => {
|
|||
|
||||
// Assert
|
||||
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
|
||||
const initialData = { companyName: 'some value' };
|
||||
const { wrapper } = getMountedComponent({}, initialData);
|
||||
const expectedName = 'some expected name';
|
||||
toTitleCase.mockImplementationOnce(() => expectedName);
|
||||
const preferredShopSectionIndex = 2;
|
||||
const preferredShopSectionIndex = 0;
|
||||
|
||||
// Act
|
||||
await tpaSubmit.beforeRouteEnter.call(
|
||||
|
|
@ -410,9 +303,9 @@ describe('tpa-submit', () => {
|
|||
|
||||
// Assert
|
||||
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
|
||||
const address = {
|
||||
streetAddress: '123 South Ln',
|
||||
|
|
@ -430,7 +323,7 @@ describe('tpa-submit', () => {
|
|||
const { wrapper } = getMountedComponent(initialStore);
|
||||
const line = 'some returned line';
|
||||
formatAddress.mockImplementationOnce(() => line);
|
||||
const preferredShopSectionIndex = 2;
|
||||
const preferredShopSectionIndex = 0;
|
||||
|
||||
// Act
|
||||
await tpaSubmit.beforeRouteEnter.call(
|
||||
|
|
@ -442,7 +335,7 @@ describe('tpa-submit', () => {
|
|||
|
||||
// Assert
|
||||
const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex];
|
||||
expect(preferredShopSection.lines[1]).toBe(line);
|
||||
expect(preferredShopSection.lines[0]).toBe(line);
|
||||
expect(formatAddress).toHaveBeenCalledTimes(1);
|
||||
expect(formatAddress).toHaveBeenCalledWith(
|
||||
address.streetAddress,
|
||||
|
|
@ -452,7 +345,7 @@ describe('tpa-submit', () => {
|
|||
address.zipCode
|
||||
);
|
||||
});
|
||||
test('third line is expected and toDisplayPhoneNumber called', async () => {
|
||||
test('second line is expected and toDisplayPhoneNumber called', async () => {
|
||||
// Arrange
|
||||
const phoneNumber = '9998887777';
|
||||
const initialStore = {
|
||||
|
|
@ -465,7 +358,7 @@ describe('tpa-submit', () => {
|
|||
const { wrapper } = getMountedComponent(initialStore);
|
||||
const expectedLine = 'returned from to display phone num';
|
||||
toDisplayPhoneNumber.mockImplementationOnce(() => expectedLine);
|
||||
const preferredShopSectionIndex = 2;
|
||||
const preferredShopSectionIndex = 0;
|
||||
|
||||
// Act
|
||||
await tpaSubmit.beforeRouteEnter.call(
|
||||
|
|
@ -477,7 +370,7 @@ describe('tpa-submit', () => {
|
|||
|
||||
// Assert
|
||||
const preferredShopSection = wrapper.vm.sections[preferredShopSectionIndex];
|
||||
expect(preferredShopSection.lines[2]).toBe(expectedLine);
|
||||
expect(preferredShopSection.lines[1]).toBe(expectedLine);
|
||||
expect(toDisplayPhoneNumber).toHaveBeenCalledWith(phoneNumber);
|
||||
});
|
||||
});
|
||||
|
|
@ -487,8 +380,14 @@ describe('tpa-submit', () => {
|
|||
const lastName = 'Eddison';
|
||||
const emailAddress = 'myname@gmail.com';
|
||||
const servicePhone = '0001112222';
|
||||
const providerCompanyName = 'Some Provider LLC';
|
||||
const initialStore = {
|
||||
order: {
|
||||
serviceLocation: {
|
||||
provider: {
|
||||
companyName: providerCompanyName
|
||||
}
|
||||
},
|
||||
contactInfo: {
|
||||
firstName,
|
||||
lastName,
|
||||
|
|
@ -498,11 +397,11 @@ describe('tpa-submit', () => {
|
|||
}
|
||||
};
|
||||
const { wrapper } = getMountedComponent(initialStore);
|
||||
const expectedLine1 = 'Jones Eddison';
|
||||
const expectedLine2 = emailAddress;
|
||||
const expectedLine3 = 'some value returned';
|
||||
toDisplayPhoneNumber.mockImplementation((number) => (number === servicePhone ? expectedLine3 : ''));
|
||||
const contactInfoSectionIndex = 3;
|
||||
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(
|
||||
|
|
@ -511,12 +410,59 @@ describe('tpa-submit', () => {
|
|||
undefined,
|
||||
(c) => c(wrapper.vm)
|
||||
);
|
||||
wrapper.vm.setSections();
|
||||
|
||||
// Assert
|
||||
const contactInfoSection = wrapper.vm.sections[contactInfoSectionIndex];
|
||||
expect(contactInfoSection.lines[0]).toBe(expectedLine1);
|
||||
expect(contactInfoSection.lines[1]).toBe(expectedLine2);
|
||||
expect(contactInfoSection.lines[2]).toBe(expectedLine3);
|
||||
expect(contactInfoSection.lines[0]).toContain(expectedEmail);
|
||||
expect(contactInfoSection.lines[0]).toContain(expectedPhone);
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
@ -524,7 +470,6 @@ describe('tpa-submit', () => {
|
|||
test.each([
|
||||
['subHeaderTitle', 'SiteSubHeaderWidget', widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT, 'site sub header'],
|
||||
['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'],
|
||||
['forwardButtonText', 'SiteFooterWidget', widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT, 'forward button text']
|
||||
])('computed %p returns expected value', (computedName, widgetLabel, fieldLabel, expected) => {
|
||||
|
|
|
|||
|
|
@ -13,53 +13,52 @@
|
|||
</div>
|
||||
<div class="iss-heritage-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
|
||||
ref="subHeaderTitle"
|
||||
:customText="subHeaderTitle"
|
||||
justifyText="center"
|
||||
:marginTopSizeOverride="4"
|
||||
class="text-color--black fs-5 tpa-submit__title--line-height" />
|
||||
class="tpa-submit-title"/>
|
||||
<textBlock
|
||||
id="tpaSubmitSubHeaderBodyOne"
|
||||
ref="tpaSubmitSubHeaderBodyOne"
|
||||
:customText="subHeaderBodyOne"
|
||||
class="small text-color--darker-gray" />
|
||||
<textBlock
|
||||
id="tpaSubmitSubHeaderBodyTwo"
|
||||
ref="tpaSubmitSubHeaderBodyTwo"
|
||||
:customText="subHeaderBodyTwo"
|
||||
class="mb-4 small text-color--darker-gray" />
|
||||
<buttonMain
|
||||
ref="buttonMainOne"
|
||||
variant="success"
|
||||
:buttonText="forwardButtonText"
|
||||
class="w-100 mb-5"
|
||||
@clickEvent="forwardButtonAction" />
|
||||
<hr class="mb-0" />
|
||||
:customText="subHeaderBodyOne" />
|
||||
<alert
|
||||
v-if="recalibrationRequired"
|
||||
ref="alertRecalWarning"
|
||||
alertClass="alert-warning recal-alert"
|
||||
:isCollapsible="true"
|
||||
:cmsWidgetName="widget.alertRecalWarning"
|
||||
@textLinkClicked="openRecalModal" />
|
||||
<modal
|
||||
ref="recalModal"
|
||||
class="recal-modal"
|
||||
:modalPosition="modalPositions.center"
|
||||
:headerText="recalModalContent.headerText"
|
||||
: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">
|
||||
<textBlock
|
||||
id="tpaSubmitServiceSummaryTitle"
|
||||
ref="tpaSubmitServiceSummaryTitle"
|
||||
:customText="serviceSummaryText"
|
||||
:marginTopSizeOverride="4"
|
||||
class="fw-bold fs-1 lh-lg text-color--black" />
|
||||
<div class="px-4 my-3">
|
||||
<div
|
||||
v-for="(section, index) in sections"
|
||||
:key="section.title">
|
||||
<hr
|
||||
v-if="index !== 0"
|
||||
class="my-3" />
|
||||
<reviewBlock
|
||||
:id="'review-block-' + index"
|
||||
:customHeaderText="section.title"
|
||||
:lines="section.lines"
|
||||
@clickEdit="() => section.onClickEdit()" />
|
||||
</div>
|
||||
<div
|
||||
v-for="(section, index) in sections"
|
||||
:key="section.title">
|
||||
<hr v-if="index !== 0" />
|
||||
<reviewBlock
|
||||
:id="'review-block-' + index"
|
||||
:customHeaderText="section.title"
|
||||
:lines="section.lines"
|
||||
:editLinkText="section.editLinkText"
|
||||
@clickEdit="() => section.onClickEdit()" />
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-0" />
|
||||
<hr />
|
||||
<div
|
||||
id="submitOrderDetailsSection"
|
||||
ref="submitOrderDetailsSection">
|
||||
|
|
@ -67,17 +66,10 @@
|
|||
id="tpaSubmitOrderDetailsTitle"
|
||||
ref="tpaSubmitOrderDetailsTitle"
|
||||
:customText="orderDetailsTitle"
|
||||
:marginTopSizeOverride="4"
|
||||
class="fw-bold text-color--black" />
|
||||
<textBlock
|
||||
id="tpaSubmitOrderDetailsBody"
|
||||
ref="tpaSubmitOrderDetailsBody"
|
||||
:customText="orderDetailsBody"
|
||||
:marginTopSizeOverride="4"
|
||||
class="mb-4 px-4 small text-color--darker-gray" />
|
||||
:marginTopSizeOverride="0"
|
||||
class="fw-bold order-details-title" />
|
||||
<deductibleBox
|
||||
ref="deductibleBox"
|
||||
class="px-3"
|
||||
:value="deductibleBoxValue" />
|
||||
</div>
|
||||
<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 contactDetailsDrawer from '@/layouts/tpa-submit/contact-details-drawer/contact-details-drawer.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
|
||||
import {
|
||||
|
|
@ -114,21 +108,16 @@ import {
|
|||
import { Form } from 'vee-validate';
|
||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||
import widgetFields from '@/constants/cms-widget-fields.js';
|
||||
import { useMainStore } from '@/store';
|
||||
import {
|
||||
toTitleCase,
|
||||
toDisplayPhoneNumber,
|
||||
formatAddress,
|
||||
formatAmountInDollars
|
||||
} 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 bailoutMessage from '@/constants/bailoutMessage';
|
||||
import submitType from '@/constants/submit-type';
|
||||
import { modalPositions } from '@/constants/component-variants';
|
||||
|
||||
const VERIFYING_COVERAGE = 'Verifying coverage';
|
||||
|
||||
|
|
@ -143,7 +132,9 @@ export default {
|
|||
siteFooter,
|
||||
contactDetailsDrawer,
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
Form
|
||||
Form,
|
||||
alert,
|
||||
modal
|
||||
},
|
||||
mixins: [BaseFormMixin],
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -154,27 +145,19 @@ export default {
|
|||
});
|
||||
},
|
||||
data() {
|
||||
const { companyName } = useMainStore().order.serviceLocation.provider;
|
||||
return {
|
||||
sections: [],
|
||||
widget: {
|
||||
siteHeader: 'SiteHeaderWidget',
|
||||
siteSubHeader: 'SiteSubHeaderWidget',
|
||||
serviceSummary: 'ServiceSummaryContent',
|
||||
subheader: {
|
||||
vehicle: 'VehicleSubTitle',
|
||||
damage: 'DamageSubTitle',
|
||||
shop: 'PreferredShopSubTitle',
|
||||
contactInfo: 'ContactDetailsSubTitle'
|
||||
},
|
||||
damageLocations: 'DamageLocationsWidget',
|
||||
orderDetails: 'OrderDetailsContent',
|
||||
footer: 'SiteFooterWidget'
|
||||
footer: 'SiteFooterWidget',
|
||||
alertIncomplete: 'AlertIncompleteWidget',
|
||||
alertRecalWarning: 'AlertRecalWarningWidget',
|
||||
editShopLinkText: 'EditShopLinkTextWidget',
|
||||
contactDetails: 'ContactDetailsSectionWidget'
|
||||
},
|
||||
companyName,
|
||||
customValueMap: {
|
||||
glassShop: companyName
|
||||
}
|
||||
modalPositions
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -197,12 +180,6 @@ export default {
|
|||
);
|
||||
return getStringWithCustomValues(cmsContent, this.customValueMap);
|
||||
},
|
||||
serviceSummaryText() {
|
||||
return this.getCmsContent(
|
||||
this.widget.serviceSummary,
|
||||
widgetFields.TEXT_BLOCK_WIDGET.TEXT
|
||||
);
|
||||
},
|
||||
orderDetailsTitle() {
|
||||
return this.getCmsContent(
|
||||
this.widget.orderDetails,
|
||||
|
|
@ -227,53 +204,21 @@ export default {
|
|||
);
|
||||
},
|
||||
isVerified() {
|
||||
return useMainStore().isVerified;
|
||||
return this.mainStore.isVerified;
|
||||
},
|
||||
currentDeductible() {
|
||||
return useMainStore().order.currentDeductible;
|
||||
return this.mainStore.order.currentDeductible;
|
||||
},
|
||||
deductibleBoxValue() {
|
||||
return this.isVerified
|
||||
? formatAmountInDollars(this.currentDeductible)
|
||||
: VERIFYING_COVERAGE;
|
||||
},
|
||||
getVehicleLines() {
|
||||
const { year, make, model } = useMainStore().order.vehicle;
|
||||
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
|
||||
);
|
||||
getPreferredShopTitle() {
|
||||
return toTitleCase(this.mainStore.order.serviceLocation.provider.companyName ?? '');
|
||||
},
|
||||
getPreferredShopLines() {
|
||||
const { phoneNumber, address } =
|
||||
useMainStore().order.serviceLocation.provider;
|
||||
const { phoneNumber, address } = this.mainStore.order.serviceLocation.provider;
|
||||
const { streetAddress, city, state, zipCode } = address;
|
||||
const displayAddress = formatAddress(
|
||||
streetAddress,
|
||||
|
|
@ -284,58 +229,86 @@ export default {
|
|||
);
|
||||
const displayPhoneNumber = toDisplayPhoneNumber(phoneNumber);
|
||||
return [
|
||||
toTitleCase(this.companyName ?? ''),
|
||||
displayAddress,
|
||||
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() {
|
||||
const { firstName, lastName, emailAddress, servicePhone } =
|
||||
useMainStore().contactInfo;
|
||||
const cmsContent = this.getCmsContent(
|
||||
this.widget.contactDetails,
|
||||
widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT
|
||||
);
|
||||
return [
|
||||
`${firstName} ${lastName}`,
|
||||
emailAddress ?? '',
|
||||
toDisplayPhoneNumber(servicePhone)
|
||||
getStringWithCustomValues(cmsContent, this.customValueMap)
|
||||
];
|
||||
},
|
||||
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: {
|
||||
setSections() {
|
||||
const vehicleScenario = useMainStore().isPolicyVehicle
|
||||
? this.navigationScenarios.EDIT_POLICY_VEHICLE
|
||||
: this.navigationScenarios.EDIT_VEHICLE;
|
||||
this.sections = [
|
||||
this.getSection(
|
||||
this.widget.subheader.vehicle,
|
||||
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.getPreferredShopTitle,
|
||||
this.getPreferredShopLines,
|
||||
() => this.navigate(this.navigationScenarios.EDIT_PREFERRED_SHOP)
|
||||
this.getEditShopLinkText,
|
||||
() => this.navigate(this.navigationScenarios.EDIT_PREFERRED_SHOP),
|
||||
),
|
||||
// eslint-disable-next-line max-len
|
||||
this.getSection(
|
||||
this.widget.subheader.contactInfo,
|
||||
this.getContactInfoTitle,
|
||||
this.getContactInfoLines,
|
||||
this.getEditContactInfoLinkText,
|
||||
this.openContactDetailsModal
|
||||
)
|
||||
];
|
||||
},
|
||||
getSection(widgetName, lines, onClick) {
|
||||
getSection(title, lines, editLinkText, onClick) {
|
||||
return {
|
||||
title: this.getCmsContent(
|
||||
widgetName,
|
||||
widgetFields.TEXT_BLOCK_WIDGET.TEXT
|
||||
),
|
||||
title: title,
|
||||
lines,
|
||||
editLinkText,
|
||||
onClickEdit: onClick
|
||||
};
|
||||
},
|
||||
|
|
@ -344,7 +317,7 @@ export default {
|
|||
await submitWorkOrder({ submitType: submitType.TPA }).then(() => {
|
||||
this.navigate(this.navigationScenarios.CLICKED_FORWARD);
|
||||
}).catch((submitError) => {
|
||||
useMainStore().setBailout(bailoutMessage.saveSessionError(submitError.data));
|
||||
this.mainStore.setBailout(bailoutMessage.saveSessionError(submitError.data));
|
||||
this.navigate(
|
||||
this.navigationScenarios.SAVE_SESSION_FAILED,
|
||||
this.$route,
|
||||
|
|
@ -372,6 +345,12 @@ export default {
|
|||
},
|
||||
openContactDetailsModal() {
|
||||
this.$refs.contactDetailsDrawer.openModal();
|
||||
},
|
||||
openRecalModal() {
|
||||
this.$refs.recalModal.openModal();
|
||||
},
|
||||
closeRecalModal() {
|
||||
this.$refs.recalModal.closeModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -387,26 +366,69 @@ export default {
|
|||
}
|
||||
|
||||
.tpa-submit {
|
||||
#tpaSubmitOrderDetailsBody {
|
||||
p {
|
||||
font-size: .875rem;
|
||||
}
|
||||
}
|
||||
.tpa-submit__title--line-height {
|
||||
line-height: map-get($spacers, 6);
|
||||
.alert-warning {
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
|
||||
.text-color--darker-gray {
|
||||
color: $darker-gray;
|
||||
.tpa-submit-title {
|
||||
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;
|
||||
}
|
||||
|
||||
hr {
|
||||
opacity: 1;
|
||||
color: $gray-350;
|
||||
margin: 1.875rem 0rem;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
|
|
|||
|
|
@ -136,7 +136,6 @@ describe('welcome-page.vue', () => {
|
|||
const state = wrapper.findComponent({ ref: 'state' });
|
||||
const glassOnlyDamage = wrapper.findComponent({ ref: 'glassOnlyDamage' });
|
||||
const phoneNumber = wrapper.findComponent({ ref: 'phoneNumber' });
|
||||
const email = wrapper.findComponent({ ref: 'email' });
|
||||
|
||||
// Assert
|
||||
expect(policyNumber.exists()).toBe(true);
|
||||
|
|
@ -146,7 +145,6 @@ describe('welcome-page.vue', () => {
|
|||
expect(state.exists()).toBe(false);
|
||||
expect(glassOnlyDamage.exists()).toBe(false);
|
||||
expect(phoneNumber.exists()).toBe(true);
|
||||
expect(email.exists()).toBe(true);
|
||||
});
|
||||
test('Policy zip field should be visible at all times', async () => {
|
||||
// Arrange
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<div class="welcome-page-container iss-heritage-content-container-width">
|
||||
<siteSubHeader
|
||||
cmsWidgetName="SiteSubHeaderWidget"
|
||||
class="mt-4" />
|
||||
class="form-group" />
|
||||
<textboxQuestion
|
||||
ref="policyNumber"
|
||||
v-model="welcomePageModel.policyNumber"
|
||||
|
|
@ -22,16 +22,6 @@
|
|||
disableAutoFill
|
||||
:isDisabled="isPolicyHolderDisabled"
|
||||
:validationRules="rules.policyNumber" />
|
||||
<textboxQuestion
|
||||
ref="policyZip"
|
||||
v-model="welcomePageModel.policyZipCode"
|
||||
inputId="policyZipCode"
|
||||
cmsWidgetName="PolicyZipQuestion"
|
||||
isRequired
|
||||
mask="#####"
|
||||
:isDisabled="isPolicyZipDisabled"
|
||||
:validationRules="rules.policyZip"
|
||||
class="mt-3" />
|
||||
<textboxQuestion
|
||||
ref="phoneNumber"
|
||||
v-model="welcomePageModel.phoneNumber"
|
||||
|
|
@ -41,15 +31,17 @@
|
|||
isRequired
|
||||
:mask="phoneMask"
|
||||
disableAutoFill
|
||||
class="mt-3" />
|
||||
placeholderText="###-###-####"
|
||||
class="form-group" />
|
||||
<textboxQuestion
|
||||
ref="extension"
|
||||
v-model="welcomePageModel.extension"
|
||||
inputId="extensionField"
|
||||
cmsWidgetName="ExtensionQuestion"
|
||||
:validationRules="rules.extension"
|
||||
maxLength="5"
|
||||
disableAutoFill
|
||||
class="mt-3" />
|
||||
class="form-group" />
|
||||
<textboxQuestion
|
||||
ref="dateOfLoss"
|
||||
v-model="welcomePageModel.dateOfLoss"
|
||||
|
|
@ -62,7 +54,7 @@
|
|||
:max="new Date().toJSON().slice(0, 10)"
|
||||
:min="'1972-12-01'"
|
||||
:validationRules="rules.lossDate"
|
||||
class="mt-3" />
|
||||
class="form-group" />
|
||||
<textBlock
|
||||
cmsWidgetName="DamageDateEstimateWidget"
|
||||
typeStyle="small"
|
||||
|
|
@ -77,44 +69,45 @@
|
|||
disableAutoFill
|
||||
:validationRules="rules.damageOption"
|
||||
placeHolderText="Select an option"
|
||||
class="mt-3" />
|
||||
class="form-group" />
|
||||
<textboxQuestion
|
||||
ref="policyZip"
|
||||
v-model="welcomePageModel.policyZipCode"
|
||||
inputId="policyZipCode"
|
||||
cmsWidgetName="PolicyZipQuestion"
|
||||
isRequired
|
||||
mask="#####"
|
||||
:isDisabled="isPolicyZipDisabled"
|
||||
:validationRules="rules.policyZip"
|
||||
class="form-group" />
|
||||
<dropdownQuestion
|
||||
v-if="displayDamageStateQuestion"
|
||||
id="welcomeDropdown"
|
||||
ref="state"
|
||||
v-model="welcomePageModel.damageState"
|
||||
class="mt-3"
|
||||
class="form-group"
|
||||
cmsWidgetName="DamageStateQuestion"
|
||||
inputId="8fdf9dc2e13e430eb57529499dceb3eb"
|
||||
:options="getStates"
|
||||
:validationRules="rules.lossState"
|
||||
isRequired
|
||||
disableAutoFill
|
||||
placeHolderText="Select an option" />
|
||||
placeHolderText="Select State" />
|
||||
<textboxQuestion
|
||||
v-if="displayDamageCityQuestion"
|
||||
ref="damageCity"
|
||||
v-model="welcomePageModel.damageCity"
|
||||
class="mt-3"
|
||||
class="form-group"
|
||||
inputId="damageCityField"
|
||||
cmsWidgetName="DamageCityQuestion"
|
||||
isRequired
|
||||
disableAutoFill
|
||||
:validationRules="rules.lossCity" />
|
||||
<textboxQuestion
|
||||
ref="email"
|
||||
v-model="welcomePageModel.email"
|
||||
inputId="emailField"
|
||||
cmsWidgetName="EmailAddressQuestion"
|
||||
:validationRules="rules.email"
|
||||
isRequired
|
||||
disableAutoFill
|
||||
class="mt-3" />
|
||||
<buttonQuestion
|
||||
v-if="displayGlassOnlyQuestion"
|
||||
ref="glassOnlyDamage"
|
||||
v-model="welcomePageModel.isDamageGlassOnly"
|
||||
class="px-0 mt-3"
|
||||
class="px-0 form-group"
|
||||
cmsWidgetName="GlassOnlyQuestion"
|
||||
inputId="isDamageGlassOnly"
|
||||
:answers="DamageGlassOnlyOptions"
|
||||
|
|
@ -142,17 +135,9 @@
|
|||
:isDismissible="false" />
|
||||
<siteFooter
|
||||
ref="siteFooter"
|
||||
class="mt-3"
|
||||
cmsWidgetName="SiteFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<textBlock
|
||||
id="requestCallbackLink"
|
||||
cmsWidgetName="HelpTextWidget"
|
||||
linkType="navigation"
|
||||
href="javascript:void(0)"
|
||||
class="mb-5 text-left"
|
||||
@clickEvent="handleHelpLinkClick" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -257,7 +242,6 @@ export default {
|
|||
duplicates: [],
|
||||
rules: {
|
||||
damageOption: 'damage-option-required',
|
||||
email: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
|
||||
extension: `${globalRules.EXTENSION_FORMAT}`,
|
||||
lossCity: `${globalRules.DATE_OF_LOSS_CITY_REQUIRED}|${globalRules.DATE_OF_LOSS_CITY_FORMAT}`,
|
||||
// eslint-disable-next-line max-len
|
||||
|
|
@ -302,7 +286,11 @@ export default {
|
|||
return !!this.getCmsContent('GlassOnlyQuestion', 'QuestionText');
|
||||
},
|
||||
getStates() {
|
||||
return states;
|
||||
return Object.keys(states).reduce((acc, key) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
acc[key] = states[key].toUpperCase();
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
isPolicyHolderDisabled() {
|
||||
return !!this.mainStore.issConfig.disabledFields.policyNumber;
|
||||
|
|
@ -323,7 +311,14 @@ export default {
|
|||
this.mainStore.updatePolicyData(this.welcomePageModel);
|
||||
const promises = [];
|
||||
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.getCoveragePolicyInfo());
|
||||
|
|
@ -408,7 +403,6 @@ export default {
|
|||
isDamageGlassOnly: this.mainStore.order.policy.isDamageGlassOnly,
|
||||
phoneNumber: this.mainStore.order.contactInfo.homePhone,
|
||||
extension: this.mainStore.order.contactInfo.extension,
|
||||
email: this.mainStore.order.customer.emailAddress,
|
||||
isPolicyNumberDisabled: this.mainStore.order.policy.isPolicyNumberDisabled
|
||||
};
|
||||
},
|
||||
|
|
@ -425,7 +419,6 @@ export default {
|
|||
this.welcomePageModel.isDamageGlassOnly = response.policy.isDamageGlassOnly;
|
||||
this.welcomePageModel.phoneNumber = response.customer.homePhone;
|
||||
this.welcomePageModel.extension = response.customer.extension;
|
||||
this.welcomePageModel.email = response.customer.emailAddress;
|
||||
},
|
||||
findDamageCause(damageCause) {
|
||||
const damageCauseOptions = this.DamageCauseOptions;
|
||||
|
|
@ -481,19 +474,13 @@ export default {
|
|||
this.mainStore.order.loadedFromCookie = true;
|
||||
this.answeredContinueModal = true;
|
||||
this.$refs.continueModal.closeModal();
|
||||
},
|
||||
handleHelpLinkClick() {
|
||||
useMainStore().setBailout(bailoutMessage.RequestCallback());
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
|
||||
this.$route
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/ux-variables-svg-strings.scss';
|
||||
form {
|
||||
.container-fluid {
|
||||
display: flex;
|
||||
|
|
@ -508,6 +495,26 @@ form {
|
|||
min-height: 1px;
|
||||
padding-left: .9375rem;
|
||||
padding-right: .9375rem;
|
||||
|
||||
.form-group {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ const navigationScenarios = Object.freeze({
|
|||
|
||||
// Policy Holder Details
|
||||
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
|
||||
CLICKED_FORWARD_LISTED_VEHICLE: 'CLICKED_FORWARD_LISTED_VEHICLE',
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ const routingTable = () => [
|
|||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_BACK,
|
||||
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
|
||||
destinationIssPageValue: issPageValues.WELCOME_PAGE
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
|
||||
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_WITH_BAILOUT,
|
||||
|
|
@ -26,7 +26,7 @@ const routingTable = () => [
|
|||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_BACK,
|
||||
destinationIssPageValue: issPageValues.VEHICLE_SELECTION
|
||||
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_WITH_REPAIR,
|
||||
|
|
@ -386,7 +386,7 @@ const routingTable = () => [
|
|||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
|
||||
destinationIssPageValue: issPageValues.VEHICLE_SELECTION
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_WITH_DUPLICATES,
|
||||
|
|
@ -394,7 +394,7 @@ const routingTable = () => [
|
|||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
|
||||
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
|
||||
destinationIssPageValue: issPageValues.VEHICLE_SELECTION
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
|
||||
|
|
@ -423,7 +423,7 @@ const routingTable = () => [
|
|||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
|
||||
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
|
||||
destinationIssPageValue: issPageValues.VEHICLE_SELECTION
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_VERIFIED_NO_VEHICLES,
|
||||
|
|
@ -435,7 +435,7 @@ const routingTable = () => [
|
|||
},
|
||||
{
|
||||
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,
|
||||
|
|
@ -451,16 +451,20 @@ const routingTable = () => [
|
|||
issPageValue: issPageValues.POLICY_HOLDER_DETAILS,
|
||||
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
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_POLICY_UNVERIFIED,
|
||||
destinationIssPageValue: issPageValues.VEHICLE_SELECTION
|
||||
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
|
||||
},
|
||||
{
|
||||
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,
|
||||
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
|
||||
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_NON_LISTED_VEHICLE,
|
||||
|
|
@ -776,11 +780,11 @@ const routingTable = () => [
|
|||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_BACK,
|
||||
destinationIssPageValue: issPageValues.POLICY_VEHICLES
|
||||
destinationIssPageValue: issPageValues.WELCOME_PAGE
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||
destinationIssPageValue: issPageValues.VEHICLE_DAMAGE
|
||||
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD_WITH_CAR_ID_NOT_FOUND,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ describe('Router', () => {
|
|||
router.push = jest.fn();
|
||||
|
||||
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', () => {
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ export const getDefaultState = () => ({
|
|||
homePhone: null,
|
||||
alternativePhone: null,
|
||||
servicePhone: null,
|
||||
extension: null,
|
||||
requestTextUpdates: false,
|
||||
notesForTechnician: ''
|
||||
},
|
||||
|
|
@ -225,7 +226,8 @@ export const getDefaultState = () => ({
|
|||
loadedFromCookie: false,
|
||||
loadedFromDupeCheck: null,
|
||||
loadedSessionClearedPreviousData: null,
|
||||
availableVaps: null
|
||||
availableVaps: null,
|
||||
visitedDuplicateCheckPage: false
|
||||
},
|
||||
applicationUser: {
|
||||
experiments: [],
|
||||
|
|
@ -243,6 +245,7 @@ export const getDefaultState = () => ({
|
|||
},
|
||||
issConfig: {
|
||||
clientName: 'Generic Insurance', // this is the default and will be overriden by the client's name
|
||||
clientFullName: 'Generic Insurance', // this is the default and will be overriden by the client's name or client's full name.
|
||||
clientDisplayName: 'Generic Insurance', // this is the default and will be overridden by the client's name or client display name.
|
||||
clientHeader: {},
|
||||
styleSheet: '', // Stylesheet used by the client.
|
||||
|
|
@ -353,6 +356,7 @@ export const useMainStore = defineStore({
|
|||
homePhone: s.order.contactInfo.homePhone,
|
||||
alternativePhone: s.order.contactInfo.alternativePhone,
|
||||
servicePhone: s.order.contactInfo.servicePhone,
|
||||
extension: s.order.contactInfo.extension,
|
||||
requestTextUpdates: s.order.contactInfo.requestTextUpdates ?? false,
|
||||
notesForTechnician: s.order.contactInfo.notesForTechnician
|
||||
}),
|
||||
|
|
@ -594,6 +598,12 @@ export const useMainStore = defineStore({
|
|||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
clearDuplicateOrders() {
|
||||
this.applicationUser.duplicateOrders = [];
|
||||
},
|
||||
updateDuplicateCheckVisited(visited) {
|
||||
this.order.visitedDuplicateCheckPage = visited;
|
||||
},
|
||||
updateCoverageStatus(status) {
|
||||
this.order.insuranceCoverage.coverageStatus = status;
|
||||
},
|
||||
|
|
@ -1011,14 +1021,13 @@ export const useMainStore = defineStore({
|
|||
});
|
||||
},
|
||||
|
||||
getProviders(serviceZipCode) {
|
||||
getProviders(serviceZipCode, shopRadiusInMiles = 100) {
|
||||
const { carId, isBigTruck } = this.order.vehicle;
|
||||
const damageType = this.damage.isRepair ? 'Repair' : 'Replace';
|
||||
const { parentAccountNumber } = this.order;
|
||||
const partsWithRecal = getTopLevelGlassPartsWithRecal(this.order.lineItems.glassParts);
|
||||
const windshieldPartWithRecal = partsWithRecal?.length > 0 ? partsWithRecal[0] : null;
|
||||
const safeliteOnly = true;
|
||||
const shopRadiusInMiles = 100;
|
||||
|
||||
let url = `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}/${parentAccountNumber}/${safeliteOnly}/${carId}`;
|
||||
if (windshieldPartWithRecal) {
|
||||
|
|
@ -1455,7 +1464,7 @@ export const useMainStore = defineStore({
|
|||
emailAddress: contactInfo.emailAddress || customer.emailAddress,
|
||||
firstName: contactInfo.firstName || customer.firstName,
|
||||
lastName: contactInfo.lastName || customer.lastName,
|
||||
homePhone: contactInfo.homePhone,
|
||||
homePhone: contactInfo.extension && contactInfo.homePhone ? contactInfo.homePhone + contactInfo.extension : contactInfo.homePhone,
|
||||
servicePhone: contactInfo.servicePhone,
|
||||
alternativePhone: contactInfo.alternativePhone,
|
||||
isSmsOptIn: contactInfo.requestTextUpdates ?? false
|
||||
|
|
@ -1589,7 +1598,8 @@ export const useMainStore = defineStore({
|
|||
order.contactInfo.firstName = data?.customer?.firstName;
|
||||
order.contactInfo.lastName = data?.customer?.lastName;
|
||||
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.alternativePhone = data?.customer?.alternativePhone;
|
||||
order.contactInfo.requestTextUpdates = data?.customer?.isSmsOptIn;
|
||||
|
|
@ -1657,7 +1667,8 @@ export const useMainStore = defineStore({
|
|||
order.contactInfo.firstName = data?.customer?.firstName;
|
||||
order.contactInfo.lastName = data?.customer?.lastName;
|
||||
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.alternativePhone = data?.customer?.alternativePhone;
|
||||
order.contactInfo.requestTextUpdates = data?.customer?.isSmsOptIn;
|
||||
|
|
@ -1719,6 +1730,7 @@ export const useMainStore = defineStore({
|
|||
order.eon = data?.eon;
|
||||
order.workOrderId = data?.workOrderId;
|
||||
order.loadedFromCookie = true;
|
||||
order.visitedDuplicateCheckPage = true;
|
||||
order.loadedSessionClearedPreviousData = false;
|
||||
issConfig.enableContinueFromCookie = false;
|
||||
this.updateIsSafeliteProvider(data?.provider?.isSafeliteProvider);
|
||||
|
|
@ -2041,6 +2053,7 @@ export const useMainStore = defineStore({
|
|||
this.order.carrierPhoneNumber = null;
|
||||
this.order.customerPortalLoginToken = null;
|
||||
this.order.loadedFromCookie = false;
|
||||
this.order.visitedDuplicateCheckPage = false;
|
||||
},
|
||||
|
||||
resetPolicy() {
|
||||
|
|
@ -2095,6 +2108,7 @@ export const useMainStore = defineStore({
|
|||
this.order.contactInfo.homePhone = null;
|
||||
this.order.contactInfo.alternativePhone = null;
|
||||
this.order.contactInfo.servicePhone = null;
|
||||
this.order.contactInfo.extension = null;
|
||||
this.order.contactInfo.requestTextUpdates = false;
|
||||
this.order.contactInfo.notesForTechnician = '';
|
||||
},
|
||||
|
|
@ -2156,6 +2170,7 @@ export const useMainStore = defineStore({
|
|||
|
||||
resetISSConfigState() {
|
||||
this.issConfig.clientName = 'Generic Insurance';
|
||||
this.issConfig.clientFullName = 'Generic Insurance';
|
||||
this.issConfig.clientDisplayName = 'Generic Insurance';
|
||||
this.issConfig.clientHeader = {};
|
||||
this.issConfig.parentAccountNumber = 0;
|
||||
|
|
@ -2275,11 +2290,11 @@ export const useMainStore = defineStore({
|
|||
this.order.policy.damageState = welcomePageModel?.damageState;
|
||||
this.order.policy.damageCity = welcomePageModel?.damageCity;
|
||||
this.order.policy.isDamageGlassOnly = welcomePageModel?.isDamageGlassOnly;
|
||||
this.order.customer.emailAddress = welcomePageModel?.email;
|
||||
this.order.serviceLocation.zipCode = welcomePageModel?.policyZipCode;
|
||||
this.updatePhoneNumbers({
|
||||
home: welcomePageModel?.phoneNumber,
|
||||
service: welcomePageModel?.phoneNumber
|
||||
service: welcomePageModel?.phoneNumber,
|
||||
extension: welcomePageModel?.extension
|
||||
});
|
||||
},
|
||||
updatePolicyHolderDetails(customerQuestions) {
|
||||
|
|
@ -2290,6 +2305,12 @@ export const useMainStore = defineStore({
|
|||
this.order.customer.address.zipCode = customerQuestions.addressQuestions.zipCode;
|
||||
this.order.customer.firstName = customerQuestions.firstName;
|
||||
this.order.customer.lastName = customerQuestions.lastName;
|
||||
this.order.customer.emailAddress = customerQuestions.email;
|
||||
this.updatePhoneNumbers({
|
||||
home: customerQuestions.phoneNumber,
|
||||
service: customerQuestions.phoneNumber,
|
||||
extension: customerQuestions.extension
|
||||
});
|
||||
this.order.serviceLocation.zipCode = customerQuestions.addressQuestions.zipCode;
|
||||
},
|
||||
updateIsSafeliteProvider(isSafelite) {
|
||||
|
|
@ -2369,23 +2390,23 @@ export const useMainStore = defineStore({
|
|||
|
||||
const isMobileApt = appointmentType === AppointmentTypeStrings.MOBILE
|
||||
|| appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
|
||||
|
||||
|
||||
const retPricedLineItems = await globalMethods.callHttpClient({
|
||||
method: endpoints.TaxOrderItems.method,
|
||||
endpoint: endpoints.TaxOrderItems.url,
|
||||
payload: {
|
||||
ParentAccountNumber: this.order.parentAccountNumber,
|
||||
BillToAccountNumber: this.billToAccountNumber,
|
||||
ProviderNumber: this.providerNumber,
|
||||
AppointmentType: appointmentType,
|
||||
PricedLineItems: getLineItemsFlattened(pricedLineItems),
|
||||
ServiceLocation: {
|
||||
City: isMobileApt ? serviceLocationCity : null,
|
||||
State: isMobileApt ? serviceLocationState : null,
|
||||
ZipCode: isMobileApt ? serviceLocationZipCode : null,
|
||||
},
|
||||
ServerData: lineItemServerData ? lineItemServerData : "",
|
||||
},
|
||||
ParentAccountNumber: this.order.parentAccountNumber,
|
||||
BillToAccountNumber: this.billToAccountNumber,
|
||||
ProviderNumber: this.providerNumber,
|
||||
AppointmentType: appointmentType,
|
||||
PricedLineItems: getLineItemsFlattened(pricedLineItems),
|
||||
ServiceLocation: {
|
||||
City: isMobileApt ? serviceLocationCity : null,
|
||||
State: isMobileApt ? serviceLocationState : null,
|
||||
ZipCode: isMobileApt ? serviceLocationZipCode : null
|
||||
},
|
||||
ServerData: lineItemServerData || ''
|
||||
}
|
||||
}).then((response) => {
|
||||
this.order.lineItems.serverData = response.data.serverData;
|
||||
return addTaxesToPricedLineItems(pricedLineItems, response.data.taxedLineItems);
|
||||
|
|
@ -2575,6 +2596,7 @@ export const useMainStore = defineStore({
|
|||
contact.homePhone = phoneNumbers.home !== undefined ? phoneNumbers.home : contact.homePhone;
|
||||
contact.alternativePhone = phoneNumbers.alternative !== undefined ? phoneNumbers.alternative : contact.alternativePhone;
|
||||
contact.servicePhone = phoneNumbers.service !== undefined ? phoneNumbers.service : contact.servicePhone;
|
||||
contact.extension = phoneNumbers.extension !== undefined ? phoneNumbers.extension : contact.extension;
|
||||
},
|
||||
|
||||
GetExperimentsByUser(userId) {
|
||||
|
|
|
|||
|
|
@ -494,18 +494,21 @@ describe('Store', () => {
|
|||
const homePhone = getRandomInt(1000000000, 9999999999);
|
||||
const servicePhone = getRandomInt(1000000000, 9999999999);
|
||||
const altPhone = getRandomInt(1000000000, 9999999999);
|
||||
const extension = getRandomInt(10000, 99999);
|
||||
|
||||
// Act
|
||||
store.updatePhoneNumbers({
|
||||
home: homePhone,
|
||||
service: servicePhone,
|
||||
alternative: altPhone
|
||||
alternative: altPhone,
|
||||
extension: extension
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(store.contactInfo.homePhone).toEqual(homePhone);
|
||||
expect(store.contactInfo.servicePhone).toEqual(servicePhone);
|
||||
expect(store.contactInfo.alternativePhone).toEqual(altPhone);
|
||||
expect(store.contactInfo.extension).toEqual(extension);
|
||||
});
|
||||
it('All null values => contact info set in store to all nulls', () => {
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -22,44 +22,6 @@ body {
|
|||
flex-direction: column;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
// Set max-width on columns to prevent overly-wide
|
||||
// components on extra wide screens.
|
||||
.col-md-6 {
|
||||
max-width: 472px;
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
max-width: 708px;
|
||||
}
|
||||
|
||||
.col {
|
||||
max-width: 236px;
|
||||
|
||||
&.one-list-card-width {
|
||||
max-width: 472px;
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
max-width: 66.6666666%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shop-question {
|
||||
.col {
|
||||
max-width: 472px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.col-xl-4 {
|
||||
max-width: 472px;
|
||||
|
||||
.col {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
//END set max-width on columns
|
||||
}
|
||||
|
||||
.pointer {
|
||||
|
|
@ -156,6 +118,10 @@ body {
|
|||
width: 46.25rem; // 740px
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
width: 60rem; // 960px
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
width: 63.75rem; // 1020px
|
||||
}
|
||||
|
|
@ -166,7 +132,7 @@ body {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
@include media-breakpoint-up(lg) {
|
||||
width: 58.33333333%;
|
||||
}
|
||||
}
|
||||
|
|
@ -174,10 +140,15 @@ body {
|
|||
div.textbox-question {
|
||||
label {
|
||||
span.sub-caption {
|
||||
color: #525656;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.site-sub-header-container {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
|
|
@ -203,10 +174,10 @@ $heritage-btn-width: 9.0625rem; // 145px
|
|||
@include heritage-btn-variant('secondary', $white, $heritage-blue-secondary, transparent, true);
|
||||
@include heritage-btn-link('link', $blue, transparent, $heritage-blue-secondary);
|
||||
|
||||
@include heritage-btn-size('lg', 0.625rem, 1.25rem);
|
||||
@include heritage-btn-size('lg', 0.625rem, 1.25rem);
|
||||
@include heritage-btn-size('md', 7px, 15px);
|
||||
@include heritage-btn-size('sm', 3px, 6px);
|
||||
@include heritage-btn-size('xs', 1px, 5px);
|
||||
@include heritage-btn-size('sm', 3px, 6px);
|
||||
@include heritage-btn-size('xs', 1px, 5px);
|
||||
|
||||
&.btn-link {
|
||||
--bs-btn-padding-x: 0px;
|
||||
|
|
|
|||
|
|
@ -115,21 +115,3 @@ caption,
|
|||
font-weight: 800;
|
||||
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;
|
||||
}
|
||||
|
|
@ -11,23 +11,25 @@ html {
|
|||
border: 1px solid $red;
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
border-radius: 60px;
|
||||
|
||||
.button-content {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include box-shadow-hover($red-200);
|
||||
z-index: 5;
|
||||
border: 1px solid $red;
|
||||
}
|
||||
|
||||
input[type="radio"]:focus+.list-button-content {
|
||||
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 {
|
||||
border: 1px solid $red;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&.ui-radio {
|
||||
|
|
@ -48,7 +50,23 @@ html {
|
|||
|
||||
// END HOVER
|
||||
|
||||
&.list-button,
|
||||
&.list-button {
|
||||
color: $red;
|
||||
|
||||
input[type="checkbox"]:focus+label,
|
||||
input[type="radio"]:focus+label {
|
||||
box-shadow: 0 0 0 2.5px $red;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked+label {
|
||||
box-shadow: 0 0 0 1px $red;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-radius: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
&.list-card {
|
||||
color: $red;
|
||||
|
||||
|
|
@ -141,6 +159,7 @@ html {
|
|||
border: 1px solid $success;
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
border-radius: 60px;
|
||||
|
||||
.button-content {
|
||||
border: none;
|
||||
|
|
@ -158,6 +177,7 @@ html {
|
|||
|
||||
&.list-card {
|
||||
border: 1px solid $success;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&.ui-radio {
|
||||
|
|
@ -178,7 +198,23 @@ html {
|
|||
|
||||
// END HOVER
|
||||
|
||||
&.list-button,
|
||||
&.list-button {
|
||||
color: $success;
|
||||
|
||||
input[type="checkbox"]:focus+label,
|
||||
input[type="radio"]:focus+label {
|
||||
box-shadow: 0 0 0 2.5px $success;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked+label {
|
||||
box-shadow: 0 0 0 1px $success;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-radius: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
&.list-card {
|
||||
color: $success;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
.base-input-button {
|
||||
&:not(.has-error):hover {
|
||||
&:not(.disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:not(.has-error):hover {
|
||||
&.list-button,
|
||||
&.list-button-horizontal,
|
||||
&.list-card {
|
||||
|
|
|
|||
|
|
@ -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-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-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";
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ $red-100: #ffe6e4;
|
|||
$red-200: #fcbfbb;
|
||||
$red-300: #f89892;
|
||||
$red-400: #e65c53;
|
||||
$red: #d4281c; // Default Red
|
||||
$red: #db0020; // Default Red
|
||||
$red-600: #ac160b;
|
||||
$red-700: #840900;
|
||||
$red-800: #5b0600;
|
||||
|
|
@ -70,8 +70,13 @@ $orange: #fd7e14;
|
|||
$teal: #20c997;
|
||||
$cyan: #0dcaf0;
|
||||
|
||||
//Heritage
|
||||
$heritage-blue-primary: #0070D1;
|
||||
$heritage-blue-secondary: #167cac;
|
||||
|
||||
// Darker gray
|
||||
$darker-gray: #525656;
|
||||
$lighter-gray: #9aa1a3;
|
||||
|
||||
// scss-docs-start colors-map
|
||||
$colors: (
|
||||
|
|
@ -153,7 +158,9 @@ $font-weight-bolder: bolder;
|
|||
$border-radius: 0.25rem;
|
||||
$border-radius-sm: 0.2rem;
|
||||
$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-list-button: 1.375rem; // Used for list buttons
|
||||
|
||||
//Progress Bar Styling
|
||||
$progress-bar-success-color: $green;
|
||||
|
|
@ -187,7 +194,9 @@ $spacers: (
|
|||
//Bootstrap Grid Breakpoints - Use these breakpoints only
|
||||
$grid-breakpoints: (
|
||||
xs: 0,
|
||||
sm: 576px,
|
||||
md: 768px,
|
||||
lg: 992px,
|
||||
xl: 1200px,
|
||||
xxl: 1440px,
|
||||
);
|
||||
|
|
@ -204,6 +213,11 @@ $alert-bg-scale: -90%;
|
|||
$alert-border-scale: -100%;
|
||||
$alert-color-scale: 40%;
|
||||
|
||||
$alert-yellow-bg: #fef6e8;
|
||||
$alert-yellow-color: #e86421;
|
||||
$alert-red-bg: #fbe9e8;
|
||||
$alert-red-color: $red;
|
||||
|
||||
//Modal animation
|
||||
// This affects all [Bootstrap] modals
|
||||
$modal-fade-transform: translate(0, 100%);
|
||||
|
|
@ -215,9 +229,8 @@ $enable-important-utilities: false;
|
|||
// Letter Spacing
|
||||
$letter-spacing-primary: 0.03em;
|
||||
// Borders
|
||||
$border-input: 1px solid rgba(179, 180, 181);
|
||||
$border-input-focus: 2.5px solid $blue;
|
||||
$border-input: 1px solid $gray-350;
|
||||
$border-input-focus: 2.5px solid $heritage-blue-primary;
|
||||
|
||||
//Heritage
|
||||
$heritage-blue-primary: #0070D1;
|
||||
$heritage-blue-secondary: #167cac;
|
||||
$svg-calendar-error-fill-color: $alert-red-bg;
|
||||
$svg-calendar-error-stroke-color: '%23db0020'; // URL encoded #db0020
|
||||
|
|
@ -1,64 +1,85 @@
|
|||
<template>
|
||||
<div
|
||||
class="alert fade show text-center mb-0 py-2 px-4"
|
||||
class="alert fade show"
|
||||
role="alert"
|
||||
:class="[
|
||||
isDismissible ? 'alert-dismissible' : '',
|
||||
alertClass,
|
||||
cssClassNameForCmsWidget,
|
||||
]">
|
||||
<p class="m-0 fw-bold alert-heading">
|
||||
{{ alertHeadline }}
|
||||
</p>
|
||||
<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" />
|
||||
<div class="alert-header">
|
||||
<svg class="alert-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="21" viewBox="0 0 20 21">
|
||||
<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"/>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
|
@ -74,6 +95,7 @@ import {
|
|||
import applicationConfig from '@/constants/application-config';
|
||||
import textBlock from '@/digital-components/text-block/text-block.vue';
|
||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||
import { Collapse } from 'bootstrap';
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
|
|
@ -84,13 +106,14 @@ export default {
|
|||
},
|
||||
props: {
|
||||
isDismissible: Boolean,
|
||||
isCollapsible: Boolean,
|
||||
/*
|
||||
alertClass class names:
|
||||
alert-success (green)
|
||||
alert-danger (red)
|
||||
alert-warning (yellow)
|
||||
alert-info (blue)
|
||||
*/
|
||||
alertClass class names:
|
||||
alert-success (green)
|
||||
alert-danger (red)
|
||||
alert-warning (yellow)
|
||||
alert-info (blue)
|
||||
*/
|
||||
alertClass: String,
|
||||
cmsWidgetName: {
|
||||
type: String,
|
||||
|
|
@ -106,7 +129,14 @@ export default {
|
|||
shouldScrollToOnMount: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
startCollapsed: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
collapsed: this.startCollapsed || false,
|
||||
collapseElement: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageQueryString() {
|
||||
|
|
@ -124,10 +154,18 @@ export default {
|
|||
},
|
||||
splitAlertCopyForParagraphTag() {
|
||||
return splitCMSCopyOnParagraphTag(this.alertCopy);
|
||||
}
|
||||
},
|
||||
collapseId() {
|
||||
return this.isCollapsible
|
||||
? `${this.cmsWidgetName}-collapse`
|
||||
: null;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.ensureAlertIsInViewPort();
|
||||
if (this.isCollapsible) {
|
||||
this.collapseElement = Collapse.getOrCreateInstance(document.getElementById(this.collapseId));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doesCopyContainRouterLink,
|
||||
|
|
@ -152,37 +190,83 @@ export default {
|
|||
<= (window.innerHeight - footerHeight
|
||||
|| document.documentElement.clientHeight - footerHeight)
|
||||
);
|
||||
}
|
||||
},
|
||||
toggleCollapse() {
|
||||
this.collapsed = !this.collapsed;
|
||||
if (this.collapsed) {
|
||||
this.collapseElement.hide();
|
||||
} else {
|
||||
this.collapseElement.show();
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.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 {
|
||||
text-decoration: none;
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
border-color: transparent;
|
||||
button {
|
||||
display: none;
|
||||
}
|
||||
.btn-close {
|
||||
background: none;
|
||||
opacity: 1;
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
}
|
||||
&.alert-dismissible {
|
||||
button {
|
||||
display: flex;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
}
|
||||
.alert-body {
|
||||
margin: 0 1rem;
|
||||
padding: .5rem 1rem .75rem 1rem;
|
||||
border-top: 1px solid;
|
||||
color: $darker-gray;
|
||||
line-height: 26px;
|
||||
font-weight: 400;
|
||||
}
|
||||
&.alert-info {
|
||||
background-color: $blue-100;
|
||||
.alert-heading {
|
||||
color: $blue-700;
|
||||
border-color: $blue-200;
|
||||
.alert-body {
|
||||
border-top-color: $blue-200;
|
||||
}
|
||||
svg {
|
||||
fill: $blue-700;
|
||||
|
|
@ -191,9 +275,10 @@ export default {
|
|||
}
|
||||
}
|
||||
&.alert-danger {
|
||||
background-color: $red-100;
|
||||
.alert-heading {
|
||||
color: $red-600;
|
||||
background-color: $alert-red-bg;
|
||||
border-color: $alert-red-color;
|
||||
.alert-body {
|
||||
border-top-color: $alert-red-color;
|
||||
}
|
||||
svg {
|
||||
fill: $red-600;
|
||||
|
|
@ -202,20 +287,20 @@ export default {
|
|||
}
|
||||
}
|
||||
&.alert-warning {
|
||||
background-color: $yellow-100;
|
||||
.alert-heading {
|
||||
color: $yellow-600;
|
||||
background-color: $alert-yellow-bg;
|
||||
border-color: $alert-yellow-color;
|
||||
.alert-body {
|
||||
border-top-color: $alert-yellow-color;
|
||||
}
|
||||
svg {
|
||||
fill: $yellow-600;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
fill: $alert-yellow-color;
|
||||
}
|
||||
}
|
||||
&.alert-success {
|
||||
background-color: $green-100;
|
||||
.alert-heading {
|
||||
color: $green-700;
|
||||
border-color: $green-200;
|
||||
.alert-body {
|
||||
border-top-color: $green-200;
|
||||
}
|
||||
svg {
|
||||
fill: $green-700;
|
||||
|
|
@ -223,9 +308,5 @@ export default {
|
|||
height: 1rem;
|
||||
}
|
||||
}
|
||||
& p {
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0.25rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||
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';
|
||||
|
||||
/** @ignore */
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import buttonVariants from '@/constants/button-variants';
|
||||
import { buttonVariants } from '@/constants/component-variants';
|
||||
import buttonSizes from '@/constants/button-sizes';
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@
|
|||
buttonWrapperClasses="list-group base-input-button list-button no-hover d-flex flex-column w-100 mb-2">
|
||||
<div
|
||||
: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
|
||||
class="m-0"
|
||||
:class="textPosition">
|
||||
|
|
@ -85,6 +88,10 @@ $heritage-checked-border-color: #0070d1;
|
|||
background: $heritage-checked-background-color;
|
||||
border-color: $heritage-checked-border-color;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
&:not(.has-sub-copy) {
|
||||
font-weight: 500;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
&:checked + .list-button-content span:nth-child(2) {
|
||||
font-weight: 400;
|
||||
|
|
@ -104,10 +111,15 @@ $heritage-checked-border-color: #0070d1;
|
|||
width: 100%;
|
||||
outline: none;
|
||||
|
||||
&:not(.has-sub-copy) {
|
||||
font-weight: 400;
|
||||
color: $darker-gray;
|
||||
}
|
||||
|
||||
span {
|
||||
&.small {
|
||||
color: $darker-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
@ -62,7 +62,7 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
a {
|
||||
color: #0070d1;
|
||||
color: $heritage-blue-primary;
|
||||
text-decoration: none;
|
||||
line-height: 1.56;
|
||||
padding: 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue