Merge branch 'develop' into feature/CSR-1395-calculate-sales-tax
This commit is contained in:
commit
d7c2789f26
89 changed files with 4673 additions and 1716 deletions
|
|
@ -1,291 +1,305 @@
|
|||
trigger:
|
||||
branches:
|
||||
include: [develop, release/*]
|
||||
paths:
|
||||
exclude:
|
||||
- deployment/*
|
||||
include:
|
||||
- "*"
|
||||
branches:
|
||||
include: [develop, release/*]
|
||||
paths:
|
||||
exclude:
|
||||
- deployment/*
|
||||
include:
|
||||
- "*"
|
||||
pr:
|
||||
branches:
|
||||
include: ["*"]
|
||||
paths:
|
||||
exclude:
|
||||
- deployment/*
|
||||
include:
|
||||
- "*"
|
||||
branches:
|
||||
include: ["*"]
|
||||
paths:
|
||||
exclude:
|
||||
- deployment/*
|
||||
include:
|
||||
- "*"
|
||||
|
||||
resources:
|
||||
containers:
|
||||
- container: node
|
||||
image: packagerepository.sagaws.net:8082/safelite/node-build:15
|
||||
- container: awscli
|
||||
image: packagerepository.sagaws.net:8082/safelite/awscli2-build:3.9
|
||||
- container: prettier-node
|
||||
image: packagerepository.sagaws.net:8082/safelite/prettier-node-build:18
|
||||
repositories:
|
||||
- repository: AzureDevOps
|
||||
type: github
|
||||
name: Safelite/AzureDevOps
|
||||
endpoint: Safelite
|
||||
ref: refs/tags/t5.5.40
|
||||
containers:
|
||||
- container: node
|
||||
image: packagerepository.sagaws.net:8082/safelite/node-build:15
|
||||
- container: awscli
|
||||
image: packagerepository.sagaws.net:8082/safelite/awscli2-build:3.9
|
||||
- container: prettier-node
|
||||
image: packagerepository.sagaws.net:8082/safelite/prettier-node-build:18
|
||||
repositories:
|
||||
- repository: AzureDevOps
|
||||
type: github
|
||||
name: Safelite/AzureDevOps
|
||||
endpoint: Safelite
|
||||
ref: refs/tags/t5.5.40
|
||||
|
||||
variables:
|
||||
- group: Digital-Infrastructure
|
||||
- group: FixMyGlass-BuildBranches
|
||||
- group: Digital-Infrastructure
|
||||
- group: FixMyGlass-BuildBranches
|
||||
|
||||
stages:
|
||||
# PR's
|
||||
- ${{ if eq(variables['Build.Reason'], 'PullRequest') }}:
|
||||
- stage: TestFormat
|
||||
displayName: Test Code Format
|
||||
jobs:
|
||||
- job: checkFormatting
|
||||
displayName: Check formatting
|
||||
container: prettier-node
|
||||
steps:
|
||||
- bash: prettier --check "$(Build.SourcesDirectory)/src/**/*.(js|vue)"
|
||||
displayName: Run Prettier check
|
||||
|
||||
- stage: TestPr
|
||||
displayName: Run Unit Tests For PullRequest
|
||||
jobs:
|
||||
- template: templates/digital/vue-jest-run-unit-tests.yml@AzureDevOps
|
||||
parameters:
|
||||
nodeContainer: node
|
||||
npmLocation: $(Build.SourcesDirectory)
|
||||
testResultsFile: junit.xml
|
||||
summaryFileLocation: $(Build.SourcesDirectory)/coverage/cobertura-coverage.xml
|
||||
- ${{ else }}:
|
||||
# Dev Build/Deploy
|
||||
- stage: Dev
|
||||
condition: eq(variables['Build.SourceBranch'], variables['dev-branch'] )
|
||||
variables:
|
||||
- group: FixMyGlassDev
|
||||
jobs:
|
||||
- deployment: devBuildDeployment
|
||||
displayName: Build and Deploy FMG - Dev
|
||||
environment: digitalCloud-dev
|
||||
container: node
|
||||
workspace:
|
||||
clean: all
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
# PR's
|
||||
- ${{ if eq(variables['Build.Reason'], 'PullRequest') }}:
|
||||
- stage: TestFormat
|
||||
displayName: Test Code Format
|
||||
jobs:
|
||||
- job: checkFormatting
|
||||
displayName: Check formatting
|
||||
container: prettier-node
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
- template: templates/digital/step-build-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
buildOutputDir: dist
|
||||
environment: Dev
|
||||
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
artifactName: vueDistDev
|
||||
awsProfile: $(devDeploymentProfile)
|
||||
outputPath: /fmg/
|
||||
deployBuckets:
|
||||
safelite-dev-fmg-us-east-1:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-1
|
||||
safelite-dev-fmg-us-east-2:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-2
|
||||
appDeployVariables:
|
||||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(apiCfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(devDeploymentProfile)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /fmg/*
|
||||
awsProfile: $(devDeploymentProfile)
|
||||
- bash: prettier --check "$(Build.SourcesDirectory)/src/**/*.(js|vue)"
|
||||
displayName: Run Prettier check
|
||||
|
||||
# Test Build/Deploy
|
||||
- stage: Test
|
||||
condition: eq(variables['Build.SourceBranch'], variables['test-branch'] )
|
||||
variables:
|
||||
- group: FixMyGlassTest
|
||||
jobs:
|
||||
- deployment: testBuildDeployment
|
||||
displayName: Build and Deploy FMG - Test
|
||||
environment: NoApproval-All
|
||||
container: node
|
||||
workspace:
|
||||
clean: all
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
- template: templates/digital/step-build-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
buildOutputDir: dist
|
||||
environment: Test
|
||||
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
artifactName: vueDistTest
|
||||
awsProfile: $(sysDeploymentProfile)
|
||||
outputPath: /fmg/
|
||||
deployBuckets:
|
||||
safelite-sys-fmg-us-east-1:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-1
|
||||
safelite-sys-fmg-us-east-2:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-2
|
||||
appDeployVariables:
|
||||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(apiCfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(sysDeploymentProfile)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /fmg/*
|
||||
awsProfile: $(sysDeploymentProfile)
|
||||
|
||||
# QA Build/Deploy
|
||||
- stage: Qa
|
||||
condition: eq(variables['Build.SourceBranch'], variables['qa-branch'] )
|
||||
variables:
|
||||
- group: FixMyGlassQa
|
||||
jobs:
|
||||
- deployment: qaBuildDeployment
|
||||
displayName: Build and Deploy FMG - QA
|
||||
environment: NoApproval-All
|
||||
container: node
|
||||
workspace:
|
||||
clean: all
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
- template: templates/digital/step-build-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
buildOutputDir: dist
|
||||
environment: Qa
|
||||
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
artifactName: vueDistQa
|
||||
awsProfile: $(qaDeploymentProfile)
|
||||
outputPath: /fmg/
|
||||
deployBuckets:
|
||||
safelite-qa-fmg-us-east-1:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-1
|
||||
safelite-qa-fmg-us-east-2:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-2
|
||||
appDeployVariables:
|
||||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(apiCfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(qaDeploymentProfile)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /fmg/*
|
||||
awsProfile: $(qaDeploymentProfile)
|
||||
- stage: TestPr
|
||||
displayName: Run Unit Tests For PullRequest
|
||||
jobs:
|
||||
- template: templates/digital/vue-jest-run-unit-tests.yml@AzureDevOps
|
||||
parameters:
|
||||
nodeContainer: node
|
||||
npmLocation: $(Build.SourcesDirectory)
|
||||
testResultsFile: junit.xml
|
||||
summaryFileLocation: $(Build.SourcesDirectory)/coverage/cobertura-coverage.xml
|
||||
- ${{ else }}:
|
||||
# Dev Build/Deploy
|
||||
- stage: Dev
|
||||
condition: eq(variables['Build.SourceBranch'], variables['dev-branch'] )
|
||||
variables:
|
||||
- group: FixMyGlassDev
|
||||
jobs:
|
||||
- deployment: devBuildDeployment
|
||||
displayName: Build and Deploy FMG - Dev
|
||||
environment: digitalCloud-dev
|
||||
container: node
|
||||
workspace:
|
||||
clean: all
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
- template: templates/digital/step-build-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
buildOutputDir: dist
|
||||
environment: Dev
|
||||
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
artifactName: vueDistDev
|
||||
awsProfile: $(devDeploymentProfile)
|
||||
outputPath: /fmg/
|
||||
deployBuckets:
|
||||
safelite-dev-fmg-us-east-1:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-1
|
||||
safelite-dev-fmg-us-east-2:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-2
|
||||
appDeployVariables:
|
||||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
|
||||
__VUE_APP_SAFELITE_HOP___: $(__VUE_APP_SAFELITE_HOP__)
|
||||
__VUE_APP_PAYPAL_SUCCESS_URL__: $(__VUE_APP_PAYPAL_SUCCESS_URL__)
|
||||
__VUE_APP_PAYPAL_CANCEL_URL__: $(__VUE_APP_PAYPAL_CANCEL_URL__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
__VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(apiCfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(devDeploymentProfile)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /fmg/*
|
||||
awsProfile: $(devDeploymentProfile)
|
||||
|
||||
# Prod Build/Deploy
|
||||
- stage: Prod
|
||||
condition: succeeded('Qa')
|
||||
variables:
|
||||
- group: FixMyGlassProd
|
||||
jobs:
|
||||
- deployment: prodBuildDeployment
|
||||
displayName: Build and Deploy FMG - Prod
|
||||
environment: digitalCloud-prod
|
||||
container: node
|
||||
workspace:
|
||||
clean: all
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
- template: templates/digital/step-build-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
buildOutputDir: dist
|
||||
environment: Prod
|
||||
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
artifactName: vueDistProd
|
||||
awsProfile: $(prodDeploymentProfile)
|
||||
outputPath: /fmg/
|
||||
deployBuckets:
|
||||
safelite-prod-fmg-us-east-1:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-1
|
||||
safelite-prod-fmg-us-east-2:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-2
|
||||
appDeployVariables:
|
||||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(apiCfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(prodDeploymentProfile)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /fmg/*
|
||||
awsProfile: $(prodDeploymentProfile)
|
||||
- template: templates/digital/auto-tag.yml@AzureDevOps
|
||||
parameters:
|
||||
dependsOn: prodBuildDeployment
|
||||
userName: SafeliteAzureDevops
|
||||
userEmail: githubazuredevops@safelite.com
|
||||
# Test Build/Deploy
|
||||
- stage: Test
|
||||
condition: eq(variables['Build.SourceBranch'], variables['test-branch'] )
|
||||
variables:
|
||||
- group: FixMyGlassTest
|
||||
jobs:
|
||||
- deployment: testBuildDeployment
|
||||
displayName: Build and Deploy FMG - Test
|
||||
environment: NoApproval-All
|
||||
container: node
|
||||
workspace:
|
||||
clean: all
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
- template: templates/digital/step-build-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
buildOutputDir: dist
|
||||
environment: Test
|
||||
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
artifactName: vueDistTest
|
||||
awsProfile: $(sysDeploymentProfile)
|
||||
outputPath: /fmg/
|
||||
deployBuckets:
|
||||
safelite-sys-fmg-us-east-1:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-1
|
||||
safelite-sys-fmg-us-east-2:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-2
|
||||
appDeployVariables:
|
||||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
|
||||
__VUE_APP_SAFELITE_HOP___: $(__VUE_APP_SAFELITE_HOP__)
|
||||
__VUE_APP_PAYPAL_SUCCESS_URL__: $(__VUE_APP_PAYPAL_SUCCESS_URL__)
|
||||
__VUE_APP_PAYPAL_CANCEL_URL__: $(__VUE_APP_PAYPAL_CANCEL_URL__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
__VUE_APP_MY_ACCOUNT__: $(__VUE_APP_MY_ACCOUNT__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(apiCfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(sysDeploymentProfile)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /fmg/*
|
||||
awsProfile: $(sysDeploymentProfile)
|
||||
|
||||
# QA Build/Deploy
|
||||
- stage: Qa
|
||||
condition: eq(variables['Build.SourceBranch'], variables['qa-branch'] )
|
||||
variables:
|
||||
- group: FixMyGlassQa
|
||||
jobs:
|
||||
- deployment: qaBuildDeployment
|
||||
displayName: Build and Deploy FMG - QA
|
||||
environment: NoApproval-All
|
||||
container: node
|
||||
workspace:
|
||||
clean: all
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
- template: templates/digital/step-build-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
buildOutputDir: dist
|
||||
environment: Qa
|
||||
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
artifactName: vueDistQa
|
||||
awsProfile: $(qaDeploymentProfile)
|
||||
outputPath: /fmg/
|
||||
deployBuckets:
|
||||
safelite-qa-fmg-us-east-1:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-1
|
||||
safelite-qa-fmg-us-east-2:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-2
|
||||
appDeployVariables:
|
||||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
|
||||
__VUE_APP_SAFELITE_HOP___: $(__VUE_APP_SAFELITE_HOP__)
|
||||
__VUE_APP_PAYPAL_SUCCESS_URL__: $(__VUE_APP_PAYPAL_SUCCESS_URL__)
|
||||
__VUE_APP_PAYPAL_CANCEL_URL__: $(__VUE_APP_PAYPAL_CANCEL_URL__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(apiCfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(qaDeploymentProfile)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /fmg/*
|
||||
awsProfile: $(qaDeploymentProfile)
|
||||
|
||||
# Prod Build/Deploy
|
||||
- stage: Prod
|
||||
condition: succeeded('Qa')
|
||||
variables:
|
||||
- group: FixMyGlassProd
|
||||
jobs:
|
||||
- deployment: prodBuildDeployment
|
||||
displayName: Build and Deploy FMG - Prod
|
||||
environment: digitalCloud-prod
|
||||
container: node
|
||||
workspace:
|
||||
clean: all
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
- template: templates/digital/step-build-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
buildOutputDir: dist
|
||||
environment: Prod
|
||||
- template: templates/digital/step-deploy-vue.yml@AzureDevOps
|
||||
parameters:
|
||||
artifactName: vueDistProd
|
||||
awsProfile: $(prodDeploymentProfile)
|
||||
outputPath: /fmg/
|
||||
deployBuckets:
|
||||
safelite-prod-fmg-us-east-1:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-1
|
||||
safelite-prod-fmg-us-east-2:
|
||||
clearFolder: true
|
||||
deployFolder: ""
|
||||
region: us-east-2
|
||||
appDeployVariables:
|
||||
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
__VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
|
||||
__VUE_APP_SAFELITE_HOP___: $(__VUE_APP_SAFELITE_HOP__)
|
||||
__VUE_APP_PAYPAL_SUCCESS_URL__: $(__VUE_APP_PAYPAL_SUCCESS_URL__)
|
||||
__VUE_APP_PAYPAL_CANCEL_URL__: $(__VUE_APP_PAYPAL_CANCEL_URL__)
|
||||
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
|
||||
indexDeployVariables:
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(apiCfDistributionId)
|
||||
paths: /*
|
||||
awsProfile: $(prodDeploymentProfile)
|
||||
- template: templates/digital/invalidate-cloudfront-cache.yml@AzureDevOps
|
||||
parameters:
|
||||
awsCliContainer: awscli
|
||||
distributionId: $(cfDistributionId)
|
||||
paths: /fmg/*
|
||||
awsProfile: $(prodDeploymentProfile)
|
||||
- template: templates/digital/auto-tag.yml@AzureDevOps
|
||||
parameters:
|
||||
dependsOn: prodBuildDeployment
|
||||
userName: SafeliteAzureDevops
|
||||
userEmail: githubazuredevops@safelite.com
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ module.exports = {
|
|||
"!src/helpers/unit-test-helper.js",
|
||||
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
|
||||
"!src/layouts/reveal/**/*.vue",
|
||||
"!src/layouts/payment-method/**/*.vue",
|
||||
"!src/fmg-components/cart/**/*.vue",
|
||||
"!src/fmg-components/add-vaps-modal-buttons/**/*.vue",
|
||||
"!src/ux-components/text-link/**/*.vue",
|
||||
"!src/layouts/vin-lookup/**/*.vue", //Temporary for Quote page testing
|
||||
"!src/common-components/funnel-header/menu-modal/**/*.vue",
|
||||
|
|
@ -24,7 +27,7 @@ module.exports = {
|
|||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 80,
|
||||
statements: 78,
|
||||
},
|
||||
},
|
||||
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
||||
|
|
|
|||
11
src/assets/img/icons/add-to-calendar.svg
Normal file
11
src/assets/img/icons/add-to-calendar.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_14504_113611)">
|
||||
<path d="M14.1762 1.52764H13.7656V1.50352C13.7656 1.10476 13.6076 0.722334 13.3263 0.44037C13.0451 0.158406 12.6636 0 12.2659 0C11.8681 0 11.4866 0.158406 11.2054 0.44037C10.9241 0.722334 10.7661 1.10476 10.7661 1.50352V1.52764H5.42476V1.50352C5.42476 1.10476 5.26675 0.722334 4.9855 0.44037C4.70424 0.158406 4.32277 0 3.92501 0C3.52725 0 3.14579 0.158406 2.86453 0.44037C2.58327 0.722334 2.42526 1.10476 2.42526 1.50352V1.52764H1.82376C1.34046 1.52891 0.877316 1.72195 0.53557 2.06455C0.193824 2.40716 0.00127018 2.87146 0 3.35598V14.1717C0.0016909 14.656 0.194379 15.1201 0.536035 15.4626C0.87769 15.8051 1.34059 15.9983 1.82376 16H14.1746C14.6581 15.9987 15.1214 15.8057 15.4634 15.4632C15.8054 15.1206 15.9983 14.6563 16 14.1717V3.35598C15.9987 2.87146 15.8062 2.40716 15.4644 2.06455C15.1227 1.72195 14.6595 1.52891 14.1762 1.52764ZM11.8889 1.50352C11.8889 1.4033 11.9286 1.30718 11.9993 1.23631C12.07 1.16544 12.1659 1.12563 12.2659 1.12563C12.3658 1.12563 12.4617 1.16544 12.5324 1.23631C12.6031 1.30718 12.6428 1.4033 12.6428 1.50352V2.99899C12.6428 3.09922 12.6031 3.19534 12.5324 3.2662C12.4617 3.33707 12.3658 3.37688 12.2659 3.37688C12.1659 3.37688 12.07 3.33707 11.9993 3.2662C11.9286 3.19534 11.8889 3.09922 11.8889 2.99899V1.50352ZM3.54807 1.50352C3.54807 1.4033 3.58778 1.30718 3.65847 1.23631C3.72916 1.16544 3.82504 1.12563 3.92501 1.12563C4.02498 1.12563 4.12086 1.16544 4.19155 1.23631C4.26224 1.30718 4.30195 1.4033 4.30195 1.50352V2.99899C4.30195 3.09922 4.26224 3.19534 4.19155 3.2662C4.12086 3.33707 4.02498 3.37688 3.92501 3.37688C3.82504 3.37688 3.72916 3.33707 3.65847 3.2662C3.58778 3.19534 3.54807 3.09922 3.54807 2.99899V1.50352ZM14.8772 14.1717C14.8747 14.3573 14.8001 14.5345 14.6691 14.6658C14.5382 14.797 14.3614 14.8719 14.1762 14.8744H1.82536C1.63995 14.8723 1.4627 14.7976 1.33144 14.6663C1.20018 14.5351 1.12531 14.3575 1.12281 14.1717V6.59296H14.8772V14.1717Z" fill="#327289"/>
|
||||
<path d="M2.33063 11.282H3.93464V12.6006C3.93464 12.7499 3.99379 12.8931 4.09907 12.9986C4.20435 13.1041 4.34715 13.1634 4.49604 13.1634C4.64494 13.1634 4.78773 13.1041 4.89301 12.9986C4.9983 12.8931 5.05745 12.7499 5.05745 12.6006V11.282H7.46346V12.6006C7.46346 12.7499 7.52261 12.8931 7.62789 12.9986C7.73318 13.1041 7.87597 13.1634 8.02486 13.1634C8.17376 13.1634 8.31655 13.1041 8.42184 12.9986C8.52712 12.8931 8.58627 12.7499 8.58627 12.6006V11.282H10.9923V12.6006C10.9923 12.7499 11.0514 12.8931 11.1567 12.9986C11.262 13.1041 11.4048 13.1634 11.5537 13.1634C11.7026 13.1634 11.8454 13.1041 11.9507 12.9986C12.0559 12.8931 12.1151 12.7499 12.1151 12.6006V11.282H13.7191C13.868 11.282 14.0108 11.2227 14.1161 11.1172C14.2214 11.0116 14.2805 10.8685 14.2805 10.7192C14.2805 10.57 14.2214 10.4268 14.1161 10.3213C14.0108 10.2157 13.868 10.1564 13.7191 10.1564H12.1151V8.84425C12.1151 8.69498 12.0559 8.55183 11.9507 8.44628C11.8454 8.34073 11.7026 8.28143 11.5537 8.28143C11.4048 8.28143 11.262 8.34073 11.1567 8.44628C11.0514 8.55183 10.9923 8.69498 10.9923 8.84425V10.1628H8.58627V8.84425C8.58627 8.69498 8.52712 8.55183 8.42184 8.44628C8.31655 8.34073 8.17376 8.28143 8.02486 8.28143C7.87597 8.28143 7.73318 8.34073 7.62789 8.44628C7.52261 8.55183 7.46346 8.69498 7.46346 8.84425V10.1628H5.05745V8.84425C5.05745 8.69498 4.9983 8.55183 4.89301 8.44628C4.78773 8.34073 4.64494 8.28143 4.49604 8.28143C4.34715 8.28143 4.20435 8.34073 4.09907 8.44628C3.99379 8.55183 3.93464 8.69498 3.93464 8.84425V10.1628H2.33063C2.18174 10.1628 2.03894 10.2221 1.93366 10.3277C1.82837 10.4332 1.76923 10.5764 1.76923 10.7257C1.76923 10.8749 1.82837 11.0181 1.93366 11.1236C2.03894 11.2292 2.18174 11.2885 2.33063 11.2885V11.282Z" fill="#327289"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_14504_113611">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -10,6 +10,7 @@ const applicationConfig = {
|
|||
PAGE_QUERYSTRING: "fmgPage",
|
||||
SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass",
|
||||
CASH_PARENT_ACCOUNT_NUMBER: 167132,
|
||||
MY_ACCOUNT: process.env.VUE_APP_MY_ACCOUNT,
|
||||
};
|
||||
|
||||
export { applicationConfig };
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ const endpoints = {
|
|||
url: "/order/api/v1/order/load-session",
|
||||
method: "POST",
|
||||
},
|
||||
GetSignature: {
|
||||
url: "/order/api/v1/order/sign",
|
||||
method: "POST",
|
||||
},
|
||||
ValidateZip: {
|
||||
url: "/location/api/v1/location/zip",
|
||||
method: "GET",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ const queryStrings = {
|
|||
FMG_PAGE: "fmgPage",
|
||||
START_TYPE: "start_type",
|
||||
ZIP_CODE: "zipcode",
|
||||
PROMO: "promo",
|
||||
ERROR: "error",
|
||||
TOKEN: "token",
|
||||
PAYERID: "payerid",
|
||||
};
|
||||
|
||||
export { queryStrings };
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ const storeActions = {
|
|||
RUN_EXPERIMENTS_FOR_TRIGGER: "runExperimentsForTrigger",
|
||||
CLEAR_VIN: "clearVin",
|
||||
RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise",
|
||||
GET_SIGNATURE: "getSignature",
|
||||
|
||||
// DEPENDENCY MUTATIONS
|
||||
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",
|
||||
|
|
@ -76,6 +77,7 @@ const storeActions = {
|
|||
SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers",
|
||||
SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers",
|
||||
SAVE_PAYMENT_TYPE: "savePaymentType",
|
||||
SAVE_PAY_NOW_TYPE: "savePayNowType",
|
||||
SAVE_PARENT_ACCOUNT_NUMBER: "saveParentAccountNumber",
|
||||
SAVE_SUPPORTING_ITEMS: "saveSupportingItems",
|
||||
SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING:
|
||||
|
|
@ -84,6 +86,7 @@ const storeActions = {
|
|||
SAVE_CUSTOMER_DETAILS: "saveCustomerDetails",
|
||||
SAVE_SERVICE_LOCATION_TECH_NOTES: "saveServiceLocationTechNotes",
|
||||
SAVE_WORK_ORDER_FLAG: "saveWorkOrderFlag",
|
||||
SAVE_PIA_ERROR_CODE: "savePiaErrorCode",
|
||||
};
|
||||
|
||||
export { storeActions };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const storeMutations = {
|
||||
UPDATE_WORK_ORDER_FLAG: "updateWorkOrderFlag",
|
||||
UPDATE_PIA_ERROR_CODE: "updatePiaErrorCode",
|
||||
|
||||
// VEHICLE MUTATIONS
|
||||
UPDATE_YEAR: "updateYear",
|
||||
|
|
@ -46,6 +47,8 @@ const storeMutations = {
|
|||
UPDATE_IS_INSURANCE: "updateIsInsurance",
|
||||
UPDATE_SAVED_SESSION_ID: "updateSavedSessionId",
|
||||
UPDATE_CRM_CUSTOMER_ID: "updateCrmCustomerId",
|
||||
UPDATE_PAY_NOW_TYPE: "updatePayNowType",
|
||||
WORK_ORDER_NUMBER: "updateWorkOrderNumber",
|
||||
|
||||
// EVENT BUS MUTATIONS
|
||||
ADD_EVENT_TO_BUS: "addEventToBus",
|
||||
|
|
|
|||
|
|
@ -192,7 +192,13 @@ export default {
|
|||
classes = "d-flex flex-row p-0";
|
||||
break;
|
||||
case "listCard":
|
||||
classes = "row g-2 justify-content-center mb-1";
|
||||
classes = "row g-2 g-md-5 justify-content-center mb-1 flex-nowrap";
|
||||
if (this.isWide) {
|
||||
classes += "flex-column";
|
||||
}
|
||||
break;
|
||||
case "addVapsButton":
|
||||
classes = "row g-4 justify-content-center mb-2";
|
||||
if (this.isWide) {
|
||||
classes += " flex-column";
|
||||
}
|
||||
|
|
@ -298,6 +304,17 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vehicle-damage {
|
||||
@include media-breakpoint-up(md) {
|
||||
.col-12 {
|
||||
width: 66.66666667%;
|
||||
}
|
||||
.col {
|
||||
width: 33.3333333%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
.button-question-overflow {
|
||||
height: calc(100vh - 274px);
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ export default {
|
|||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:checked[type="checkbox"] {
|
||||
background-color: $blue;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.form-check-input {
|
||||
|
|
|
|||
|
|
@ -148,12 +148,25 @@ export default {
|
|||
}
|
||||
&.modal-component {
|
||||
.modal-dialog {
|
||||
max-width: 576px;
|
||||
max-width: 767px;
|
||||
margin: 0 auto;
|
||||
@include media-breakpoint-up(md) {
|
||||
width: 376px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(100%, 0);
|
||||
}
|
||||
.modal-content {
|
||||
margin: 0 auto;
|
||||
margin: 1.5rem auto 0 auto;
|
||||
box-shadow: 0px 16px 48px -16px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 1.5rem 1.5rem 0 0;
|
||||
@include media-breakpoint-up(md) {
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
height: 100vh;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.modal-body {
|
||||
.modal-sub-body {
|
||||
color: $gray-600;
|
||||
|
|
@ -166,6 +179,9 @@ export default {
|
|||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.modal-dialog-centered {
|
||||
|
|
@ -184,6 +200,11 @@ export default {
|
|||
&.show .modal-dialog {
|
||||
transform: translateY(0);
|
||||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
&.fade {
|
||||
transition: opacity 0.25s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
body {
|
||||
.modal-backdrop {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,218 @@
|
|||
<template>
|
||||
<baseInputButton
|
||||
v-bind="$props"
|
||||
:buttonWrapperClasses="[
|
||||
'add-vaps-button w-100 rounded-3 d-flex align-items-center h-100 base-input-button',
|
||||
{ horizontal: isWide },
|
||||
]"
|
||||
v-model="selectedValue">
|
||||
<div
|
||||
class="d-flex w-100 align-items-center px-1 h-100 add-vaps-button-content button-content rounded-3"
|
||||
:class="labelClasses">
|
||||
<img :id="buttonImageId" :src="buttonImage" :alt="altText" />
|
||||
<div class="copy-wrapper" :class="isWide ? 'ms-4 me-2' : ''">
|
||||
<p class="small mb-0" :class="isWide ? 'mt-0' : 'mt-1'">
|
||||
{{ buttonLabel }}
|
||||
</p>
|
||||
<p v-if="buttonLabelSubCopy" class="fs-7 m-0 sub-copy">
|
||||
{{ buttonLabelSubCopy }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="fs-7 mb-0 link-styling" :class="isWide ? 'mt-0' : 'mt-2'">View details</p>
|
||||
</div>
|
||||
</baseInputButton>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import baseInputButton from "@/digital-components/base-input-button/base-input-button";
|
||||
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||
|
||||
export default {
|
||||
name: "addVapsButton",
|
||||
mixins: [inputButtonWrapperMixin],
|
||||
components: {
|
||||
baseInputButton,
|
||||
},
|
||||
computed: {
|
||||
labelClasses() {
|
||||
if (this.isWide) {
|
||||
let classes = "flex-row py-2 ps-4 pe-4";
|
||||
if (this.buttonLabelSubCopy) {
|
||||
classes += " checkboxTop";
|
||||
}
|
||||
return classes;
|
||||
} else {
|
||||
return "flex-column py-2";
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@mixin add-vaps-button-focus($box-shadow-color) {
|
||||
&:focus-visible + .add-vaps-button-content {
|
||||
box-shadow: 0 0 0 2.5px $box-shadow-color;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
&:focus + .add-vaps-button-content {
|
||||
box-shadow: 0 0 0 2.5px $box-shadow-color;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
&:checked:focus + .add-vaps-button-content {
|
||||
box-shadow: 0 0 0 2.5px $box-shadow-color;
|
||||
}
|
||||
}
|
||||
.add-vaps-button {
|
||||
border: 1px solid $gray-500;
|
||||
&.has-error {
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
@include add-vaps-button-focus($red);
|
||||
}
|
||||
}
|
||||
img {
|
||||
height: auto;
|
||||
width: 2.6rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
.copy-wrapper {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.link-styling {
|
||||
font-weight: 500;
|
||||
color: $blue;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
position: absolute;
|
||||
|
||||
+ .add-vaps-button-content {
|
||||
outline: none;
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
|
||||
p {
|
||||
color: $gray-600;
|
||||
text-align: center;
|
||||
|
||||
&.sub-copy {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include add-vaps-button-focus($blue);
|
||||
|
||||
+ .add-vaps-button-content::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 3rem 0 0 0;
|
||||
background: white;
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 2px;
|
||||
order: 2;
|
||||
flex-shrink: 0;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
+ .add-vaps-button-content.checkboxTop::before {
|
||||
margin: -1.25rem 0.5rem 0 0 !important;
|
||||
}
|
||||
|
||||
+ .add-vaps-button-content.checkboxTop::after {
|
||||
margin: -1.5rem 0.5rem 0 0 !important;
|
||||
}
|
||||
|
||||
&:checked + .add-vaps-button-content::before {
|
||||
background: $blue;
|
||||
}
|
||||
|
||||
&:checked + .add-vaps-button-content::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
margin: 3.2rem 0 0 0;
|
||||
border-left: 2px solid $white;
|
||||
border-bottom: 2px solid $white;
|
||||
height: 6px;
|
||||
width: 11px;
|
||||
transform: rotate(-45deg);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
+ .add-vaps-button-content::before {
|
||||
content: "";
|
||||
display: none;
|
||||
}
|
||||
|
||||
+ .add-vaps-button-content::after {
|
||||
content: "";
|
||||
display: none;
|
||||
}
|
||||
|
||||
+ .add-vaps-button-content {
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
+ .add-vaps-button-content::before {
|
||||
content: "";
|
||||
position: relative;
|
||||
margin: 0 0.5rem 0 0;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
&:checked + .add-vaps-button-content::after {
|
||||
content: "";
|
||||
margin: -0.15rem 0 0 0;
|
||||
left: 1.175rem;
|
||||
}
|
||||
|
||||
&:checked + .add-vaps-button-content {
|
||||
p {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sub-copy {
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
+ .add-vaps-button-content {
|
||||
outline: none;
|
||||
min-height: 48px;
|
||||
color: $gray-600;
|
||||
|
||||
img {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $gray-600;
|
||||
text-align: left;
|
||||
|
||||
&.sub-copy {
|
||||
color: $gray-550;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="add-vaps-buttons" aria-live="polite" v-if="isDisplayed">
|
||||
<buttonQuestion
|
||||
ref="buttonQuestion"
|
||||
:answers="answersToDisplay"
|
||||
groupName="addVaps"
|
||||
v-model="selectedValues"
|
||||
buttonTypeString="addVapsButton"
|
||||
:buttonTypeObject="addVapsButton"
|
||||
:isWide="answersToDisplay.length > 1 ? false : true" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
import addVapsButton from "./add-vaps-button/add-vaps-button";
|
||||
|
||||
export default {
|
||||
name: "add-vaps-modal-buttons",
|
||||
props: {
|
||||
cart: Object,
|
||||
rainDefenseItem: Object,
|
||||
wiperItems: Object,
|
||||
vapsTilesCmsName: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addVapsButton: addVapsButton,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedValues: {
|
||||
get: function () {
|
||||
return this.answersToDisplay;
|
||||
},
|
||||
set: function (newValue) {
|
||||
// TODO - emit event to open modal
|
||||
},
|
||||
},
|
||||
isDisplayed() {
|
||||
return this.showAddRainDefense || this.showAddWipers;
|
||||
},
|
||||
showAddRainDefense() {
|
||||
// if it's not found in the cart
|
||||
return !this.cart.some((vap) => {
|
||||
return vap.partType === "RAIN DEFENSE";
|
||||
});
|
||||
},
|
||||
showAddWipers() {
|
||||
// if it's not found in the cart
|
||||
return !this.cart.some((vap) => {
|
||||
return vap.partType.includes(" WIPER");
|
||||
});
|
||||
},
|
||||
rainDefensePrice() {
|
||||
const lineItem = this.rainDefenseItem;
|
||||
const price = lineItem.laborAmount + lineItem.sellingPrice + lineItem.kitPrice;
|
||||
return parseFloat(price).toFixed(2);
|
||||
},
|
||||
wipersPrice() {
|
||||
let price = 0;
|
||||
const lineItems = this.wiperItems;
|
||||
lineItems?.forEach((item) => {
|
||||
price += item.laborAmount + item.sellingPrice + item.kitPrice;
|
||||
});
|
||||
return parseFloat(price).toFixed(2);
|
||||
},
|
||||
answersCmsData() {
|
||||
return this.getCmsContent(this.vapsTilesCmsName, "Answers");
|
||||
},
|
||||
answersToDisplay() {
|
||||
const answers = [];
|
||||
if (!this.answersCmsData) return answers;
|
||||
|
||||
const getAnswer = (name, answers) => {
|
||||
const answerIndex = answers.findIndex((answer) => {
|
||||
return answer.Name === name;
|
||||
});
|
||||
return answers[answerIndex];
|
||||
};
|
||||
|
||||
if (this.showAddWipers) {
|
||||
const answer = getAnswer("ADD WIPER BLADES", this.answersCmsData);
|
||||
answer.SubText = "+$" + this.wipersPrice;
|
||||
answers.push(answer);
|
||||
}
|
||||
if (this.showAddRainDefense) {
|
||||
const answer = getAnswer("ADD RAIN DEFENSE", this.answersCmsData);
|
||||
answer.SubText = "+$" + this.rainDefensePrice;
|
||||
answers.push(answer);
|
||||
}
|
||||
|
||||
return answers;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
351
src/fmg-components/cart/cart.vue
Normal file
351
src/fmg-components/cart/cart.vue
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
<template>
|
||||
<div class="cart">
|
||||
<div class="container-fluid px-0">
|
||||
<div
|
||||
class="row vin-toggle flex align-items-center"
|
||||
:class="[isActive ? 'active' : '']"
|
||||
@click="toggleClass()">
|
||||
<div class="col d-flex justify-content-between">
|
||||
<span class="label">Amount Due</span
|
||||
><span class="label amount-due">$321.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="price-table">
|
||||
<div class="service-type" :class="{ packageWithVAPS: packageWithVAPS }">
|
||||
<textBlock :cmsWidgetName="packageNameWidget" /><span>{{ packagePrice }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(lineItem, i) in displayedPackageLineItems"
|
||||
:key="i"
|
||||
:class="[this.displayedPackageLineItems ? 'vaps' : '']">
|
||||
<span>{{ lineItem.name }}</span>
|
||||
<span><a href="#">Remove</a></span>
|
||||
</div>
|
||||
<hr style="background: red" />
|
||||
<div
|
||||
v-for="(lineItem, i) in displayedNonPackageLineItems"
|
||||
:key="i"
|
||||
:class="[this.displayedNonPackageLineItems ? 'vaps' : '']">
|
||||
<span>{{ lineItem.name }}</span>
|
||||
<span><a href="#">Remove</a></span>
|
||||
<span>{{ lineItem.price }}</span>
|
||||
</div>
|
||||
<div v-if="hasRecycleLineItem">
|
||||
<span>{{ recycleLabel }}</span
|
||||
><span>{{ recycleFee }}</span>
|
||||
</div>
|
||||
<!-- Sub total, sales tax, total columns -->
|
||||
<div class="sub-total"><span>Subtotal</span><span>$320</span></div>
|
||||
<div class="sales-tax"><span>Sales tax</span><span>$1</span></div>
|
||||
<div class="amount-due"><span>Amount due</span><span>$321</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import cart from "@/fmg-components/cart/cart";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import { packageNames } from "@/constants/package-names";
|
||||
import {
|
||||
getHighestFullySatisfiedTier,
|
||||
containsLineItemWithPartType,
|
||||
getPackageContents,
|
||||
findLineItemsWithPartType,
|
||||
} from "@/helpers/service-package-helper";
|
||||
import reviewBlock from "@/layouts/review/review-block/review-block";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
|
||||
export default {
|
||||
name: "modal",
|
||||
props: {
|
||||
headerCmsWidgetName: String,
|
||||
servicePackageOptionsCmsName: String,
|
||||
damage: Object,
|
||||
orderLineItems: Object,
|
||||
availableLineItems: Object,
|
||||
allowableCartItems: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
//availableVaps: [], TO DO Do we need this?
|
||||
packageWithVAPS: false,
|
||||
// packageLevel: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleClass: function (event) {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
initializeComponent(apiResponses) {
|
||||
//TO DO Do we need this?
|
||||
// const vapsFromApi = [...apiResponses.wipers, apiResponses.rainDefense];
|
||||
// this.availableVaps = vapsFromApi;
|
||||
},
|
||||
getPackagePriceString(packageName) {
|
||||
const formattedPriceFloat = parseFloat(this.getPackagePrice(packageName)).toFixed(2);
|
||||
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;
|
||||
},
|
||||
getPackagePrice(packageName) {
|
||||
let priceFloat = this.isInsuranceSelected
|
||||
? 0
|
||||
: baseMixin.methods.getTierOnePackagePrice(
|
||||
baseMixin.methods.filterOutFees(this.nullSafeAvailableLineItems)
|
||||
);
|
||||
|
||||
priceFloat += this.getVapsPrice(packageName);
|
||||
|
||||
return priceFloat;
|
||||
},
|
||||
getVapsPrice(packageName) {
|
||||
const vapsItems = this.getVapsLineItemsForSelectedPackage(packageName);
|
||||
|
||||
let price = 0;
|
||||
|
||||
vapsItems.forEach((item) => {
|
||||
price += baseMixin.methods.getTotalLineItemPrice(item);
|
||||
});
|
||||
|
||||
return price;
|
||||
},
|
||||
//START HERE
|
||||
getVapsLineItemsForSelectedPackage(packageName) {
|
||||
const packageContentTypes = getPackageContents(
|
||||
this.glassToReplace,
|
||||
this.nullSafeAvailableLineItems,
|
||||
this.isRepair,
|
||||
packageName
|
||||
);
|
||||
|
||||
let vapsLineItemsForSelectedPackage = [];
|
||||
|
||||
packageContentTypes.forEach((vapType) => {
|
||||
vapsLineItemsForSelectedPackage.push(
|
||||
...findLineItemsWithPartType(vapType, this.nullSafeAvailableLineItems)
|
||||
);
|
||||
});
|
||||
// console.log("VapsLineItems:", vapsLineItemsForSelectedPackage);
|
||||
return vapsLineItemsForSelectedPackage;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
packageNameWidget() {
|
||||
const servicePackageNames = this.getCmsContent(
|
||||
this.servicePackageOptionsCmsName,
|
||||
"Answers"
|
||||
);
|
||||
|
||||
if (!servicePackageNames) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const currentPackage = servicePackageNames.find(
|
||||
(entry) => entry.Name === this.packageLevel
|
||||
);
|
||||
|
||||
return currentPackage.SubWidgetName;
|
||||
},
|
||||
packageLevel() {
|
||||
const tier = getHighestFullySatisfiedTier(
|
||||
this.glassToReplace,
|
||||
this.availableLineItems,
|
||||
this.isRepair,
|
||||
this.vaps
|
||||
);
|
||||
return tier;
|
||||
},
|
||||
packagePrice() {
|
||||
let packagePriceString;
|
||||
if (this.packageLevel.length > 0) {
|
||||
packagePriceString = this.getPackagePriceString(this.packageLevel);
|
||||
}
|
||||
return packagePriceString;
|
||||
// return (packagePriceString.length > 0) ? packagePriceString : "Test String";
|
||||
},
|
||||
displayedPackageLineItems() {
|
||||
const displayedPackageLineItems = [];
|
||||
const packageLineItems = this.getVapsLineItemsForSelectedPackage(this.packageLevel);
|
||||
//TO DO Could we get the same list from this.orderLineItems?
|
||||
packageLineItems?.forEach((item) => {
|
||||
let price = item.sellingPrice + item.kitPrice + item.laborAmount;
|
||||
const packageLineItem = {
|
||||
name: item.partType,
|
||||
price: "$" + parseFloat(price).toFixed(2),
|
||||
partType: item.partType,
|
||||
isRemovable: item.isRemovable,
|
||||
};
|
||||
displayedPackageLineItems.push(packageLineItem);
|
||||
});
|
||||
// console.log("What displayedPackageLineItems is", packageLineItems);
|
||||
return displayedPackageLineItems;
|
||||
},
|
||||
displayedNonPackageLineItems() {
|
||||
const displayedNonPackageLineItems = [];
|
||||
const possibleLineItems = [...this.vaps, ...this.supportingItems];
|
||||
possibleLineItems?.forEach((item) => {
|
||||
let price = item.sellingPrice + item.kitPrice + item.laborAmount;
|
||||
const isAllowed = this.allowableLineItems?.findIndex((lineItem) => {
|
||||
return lineItem.partType === item.partType;
|
||||
});
|
||||
const indexFound = this.displayedPackageLineItems?.findIndex((packageLineItem) => {
|
||||
return packageLineItem.partType === item.partType;
|
||||
});
|
||||
const isInPackage = this.displayedPackageLineItems?.findIndex((packageLineItem) => {
|
||||
return packageLineItem.partType === item.partType;
|
||||
});
|
||||
if (isInPackage === -1 && isAllowed > -1) {
|
||||
const nonPackageLineItem = {
|
||||
name: item.partType,
|
||||
price: "$" + parseFloat(price).toFixed(2),
|
||||
partType: item.partType,
|
||||
isRemovable: item.isRemovable,
|
||||
};
|
||||
displayedNonPackageLineItems.push(nonPackageLineItem);
|
||||
}
|
||||
});
|
||||
return displayedNonPackageLineItems;
|
||||
},
|
||||
glassToReplace() {
|
||||
if (!this.damage) {
|
||||
return;
|
||||
}
|
||||
return this.damage.glassToReplace;
|
||||
},
|
||||
isRepair() {
|
||||
if (!this.damage) {
|
||||
return;
|
||||
}
|
||||
return this.damage.isRepair;
|
||||
},
|
||||
vaps() {
|
||||
const vaps = this.orderLineItems?.vaps;
|
||||
return vaps?.length > 0 ? vaps : [];
|
||||
},
|
||||
supportingItems() {
|
||||
const supportingItems = this.orderLineItems?.supportingItems;
|
||||
return supportingItems?.length > 0 ? supportingItems : [];
|
||||
},
|
||||
recycleLabel() {
|
||||
return "Recycling";
|
||||
},
|
||||
recycleFee() {
|
||||
return "$35.00";
|
||||
},
|
||||
hasRecycleLineItem() {
|
||||
//TO DO
|
||||
// if (this.lineItems.supportingItems[0].partNumber === "RECYCLE FEE") {
|
||||
// return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
return true;
|
||||
},
|
||||
nullSafeAvailableLineItems() {
|
||||
return this.availableLineItems ?? [];
|
||||
},
|
||||
},
|
||||
components: {
|
||||
textBlock,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cart {
|
||||
h5 {
|
||||
border-bottom: 1px solid $gray-500;
|
||||
color: $black;
|
||||
}
|
||||
.border-bottom {
|
||||
color: $gray;
|
||||
}
|
||||
.label {
|
||||
font-weight: 500;
|
||||
}
|
||||
.amount-due {
|
||||
color: $green;
|
||||
}
|
||||
.price-table {
|
||||
max-height: 0;
|
||||
transition: all 350ms ease-in;
|
||||
overflow: hidden;
|
||||
div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1.5rem;
|
||||
&:nth-child(odd) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
&:nth-last-child(-n + 3) {
|
||||
background-color: #e3f2ea;
|
||||
}
|
||||
&:last-child {
|
||||
background-color: #0c7e47;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.vaps {
|
||||
background-color: #f5f5f5;
|
||||
padding-left: 2.5rem;
|
||||
align-items: center;
|
||||
span {
|
||||
display: flex;
|
||||
width: 33.33333%;
|
||||
&:nth-child(2) {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
&:last-child {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
.package-type,
|
||||
.service-type {
|
||||
//background-color: #f5f5f5;
|
||||
align-items: center;
|
||||
}
|
||||
.sub-total,
|
||||
.sales-tax,
|
||||
.amount-due {
|
||||
font-weight: 500;
|
||||
}
|
||||
.sub-total {
|
||||
border-top: 1px solid #0c7e47;
|
||||
}
|
||||
.service-type {
|
||||
// background-color: #f5f5f5;
|
||||
.text-block {
|
||||
background: transparent;
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.vin-toggle {
|
||||
&:after {
|
||||
content: "";
|
||||
transition: all 0.5s ease;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.9' xml:space='preserve'%3e%3cpath d='M8 8.9c-.2 0-.5-.1-.6-.3L.3 1.5C.1 1.4 0 1.1 0 .9 0 .7.1.4.3.3.4.1.7 0 .9 0c.2 0 .5.1.6.3L8 6.7 14.5.2c.1-.1.4-.2.6-.2.2 0 .5.1.6.3s.3.4.3.6c0 .2-.1.5-.3.6L8.6 8.6c-.1.2-.4.3-.6.3z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right center;
|
||||
width: 16px;
|
||||
height: 9px;
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
right: 0.75rem;
|
||||
margin: 1rem 0 1rem 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
&.active:after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
&.active + .price-table {
|
||||
max-height: 650px;
|
||||
transition: all 350ms ease-in;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
</div>
|
||||
<alert
|
||||
v-if="displayGlobalAlert"
|
||||
class="position-absolute rounded-0 w-100 border-0 shadow-sm"
|
||||
class="rounded-0 w-100 border-0 shadow-sm"
|
||||
cmsWidgetName="GlobalAlert"
|
||||
:manualHeadline="globalAlertMessage.messageHeadline"
|
||||
:manualCopy="globalAlertMessage.messageCopy"
|
||||
|
|
@ -63,14 +63,11 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.funnel-header {
|
||||
position: relative;
|
||||
padding: 0.97rem 0;
|
||||
}
|
||||
|
||||
.logo-image {
|
||||
max-width: 78px;
|
||||
}
|
||||
.alert {
|
||||
left: 0;
|
||||
top: 72px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,16 @@
|
|||
<template>
|
||||
<div class="menu-modal-container">
|
||||
<button
|
||||
class="menu-button"
|
||||
type="button"
|
||||
:class="[isActive ? 'active' : '']"
|
||||
@click="toggleModal"
|
||||
aria-label="Hamburger Menu (modal window)">
|
||||
<div class="bar1"></div>
|
||||
<div class="bar2"></div>
|
||||
<div class="bar3"></div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="menu-modal-container">
|
||||
<button
|
||||
class="menu-button"
|
||||
|
|
@ -20,18 +32,6 @@
|
|||
aria-labelledby="footerModalLabel"
|
||||
aria-hidden="true"
|
||||
v-on="{ 'show.bs.modal': show, 'hide.bs.modal': hide }">
|
||||
<div class="menu-modal-container">
|
||||
<button
|
||||
class="menu-button"
|
||||
type="button"
|
||||
:class="[isActive ? 'active' : '']"
|
||||
@click="toggleModal"
|
||||
aria-label="Hamburger Menu (modal window)">
|
||||
<div class="bar1"></div>
|
||||
<div class="bar2"></div>
|
||||
<div class="bar3"></div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-dialog modal-fullscreen">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header visually-hidden">
|
||||
|
|
@ -158,7 +158,6 @@ export default {
|
|||
}
|
||||
.modal {
|
||||
&.menu-modal {
|
||||
max-width: 576px;
|
||||
left: auto;
|
||||
height: calc(100% - 72px);
|
||||
top: 72px;
|
||||
|
|
@ -175,7 +174,6 @@ export default {
|
|||
}
|
||||
.modal-fullscreen {
|
||||
width: 100vw;
|
||||
max-width: 576px;
|
||||
}
|
||||
.navigation-link {
|
||||
margin-bottom: 1.5rem;
|
||||
|
|
@ -188,7 +186,7 @@ export default {
|
|||
position: absolute;
|
||||
padding: 1.47rem 1rem 1.47rem 1.47rem;
|
||||
right: 0;
|
||||
top: -4.5rem;
|
||||
top: -5rem;
|
||||
button {
|
||||
border: none;
|
||||
&.menu-button {
|
||||
|
|
|
|||
|
|
@ -1,34 +1,51 @@
|
|||
<template>
|
||||
<div :class="`page-container-grouped-styles questions-page`">
|
||||
<div :class="`questions-page`">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
cmsWidgetName="alertWidget"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="alertFewMoreQuestionsHeader"
|
||||
:manualCopy="alertFewMoreQuestionsCopy"
|
||||
v-bind:isDismissible="false" />
|
||||
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
|
||||
<questionChain
|
||||
ref="questionChain"
|
||||
v-model="selectedAnswers[questionsDatum.answerKey]"
|
||||
:questionData="questionsDatum.questions"
|
||||
:index="i"
|
||||
v-if="showThisQuestionChain(questionsDatum, i)"
|
||||
:answerKey="questionsDatum.answerKey"
|
||||
:validationRules="validationRules" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<alert
|
||||
ref="alertFewMoreQuestions"
|
||||
cmsWidgetName="alertWidget"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="alertFewMoreQuestionsHeader"
|
||||
:manualCopy="alertFewMoreQuestionsCopy"
|
||||
v-bind:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key">
|
||||
<questionChain
|
||||
ref="questionChain"
|
||||
v-model="selectedAnswers[questionsDatum.answerKey]"
|
||||
:questionData="questionsDatum.questions"
|
||||
:index="i"
|
||||
v-if="showThisQuestionChain(questionsDatum, i)"
|
||||
:answerKey="questionsDatum.answerKey"
|
||||
:validationRules="validationRules" />
|
||||
</div>
|
||||
|
||||
<navbar
|
||||
ref="navbar"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!isMetaValid"
|
||||
@back-clicked="handleBackButtonAction"
|
||||
@ForwardClicked="handleForwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!isMetaValid"
|
||||
@back-clicked="handleBackButtonAction"
|
||||
@ForwardClicked="handleForwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -40,7 +57,7 @@ import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
|||
import alert from "@/ux-components/alert/alert";
|
||||
import questionChain from "@/digital-components/question-chain/question-chain";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
|
||||
export default {
|
||||
|
|
@ -88,7 +105,7 @@ export default {
|
|||
alert,
|
||||
questionChain,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
loadingModal,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div v-if="isModalVisible">
|
||||
<div class="dimmer-overlay"></div>
|
||||
<div class="container-fluid modal-loader" :class="layoutClass">
|
||||
<div v-if="notFullScreen" class="dimmer-overlay"></div>
|
||||
<div class="modal-loader" :class="layoutClass">
|
||||
<div class="row g-2 h-100 d-flex align-items-center">
|
||||
<div class="container-fluid overflow-hidden">
|
||||
<div class="row h-100">
|
||||
|
|
@ -69,7 +69,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
layoutClass() {
|
||||
return this.notFullScreen ? "card-layout" : "full-screen";
|
||||
return this.notFullScreen ? "card-layout" : "full-screen container-fluid";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -123,12 +123,20 @@ export default {
|
|||
}
|
||||
|
||||
&.card-layout {
|
||||
height: 186px;
|
||||
width: calc(100% - 3rem);
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
position: absolute;
|
||||
top: calc(50% - 93px);
|
||||
left: 1.5rem;
|
||||
|
||||
& .text-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .mb-4 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tagbg {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { mount } from "@vue/test-utils";
|
||||
import funnelFooter from "./funnel-footer";
|
||||
import navbar from "./nav-bar";
|
||||
|
||||
describe("funnel-footer.vue", () => {
|
||||
describe("nav-bar.vue", () => {
|
||||
test("Should emit ForwardClicked on button click", async () => {
|
||||
// Act
|
||||
const wrapper = mount(funnelFooter, {
|
||||
const wrapper = mount(navbar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
wrapper.vm.buttonClick();
|
||||
|
|
@ -14,7 +14,7 @@ describe("funnel-footer.vue", () => {
|
|||
|
||||
test("Should emit BackClicked on link click", async () => {
|
||||
// Act
|
||||
const wrapper = mount(funnelFooter, {
|
||||
const wrapper = mount(navbar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
wrapper.vm.linkClick();
|
||||
|
|
@ -24,7 +24,7 @@ describe("funnel-footer.vue", () => {
|
|||
|
||||
test("Should change button text when update button text is called", async () => {
|
||||
// Act
|
||||
const wrapper = mount(funnelFooter, {
|
||||
const wrapper = mount(navbar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
wrapper.vm.updateButtonText("newText");
|
||||
|
|
@ -35,7 +35,7 @@ describe("funnel-footer.vue", () => {
|
|||
|
||||
test("should run removeLoader fn on buttonMain and return false for onkeydown fn", async () => {
|
||||
// Arrange
|
||||
const wrapper = mount(funnelFooter, {
|
||||
const wrapper = mount(navbar, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
<template>
|
||||
<div class="row"></div>
|
||||
<footer class="footer container-fluid g-5 my-5 px-0" id="infoBox">
|
||||
<div class="row d-flex flex-row-reverse align-items-center vw-100">
|
||||
<div class="col button-col d-flex" id="stacked">
|
||||
<div class="nav-bar container-fluid g-5 my-5 px-0" id="infoBox">
|
||||
<div class="row d-flex flex-row-reverse justify-content-between align-items-center vw-100">
|
||||
<div
|
||||
:class="['col button-col d-flex', buttonSize ? 'large-button' : 'medium-button']"
|
||||
id="stacked">
|
||||
<buttonMain
|
||||
ref="buttonMain"
|
||||
isPrimary
|
||||
|
|
@ -13,7 +15,7 @@
|
|||
:isDisabled="isForwardActionDisabled"
|
||||
@click-event="buttonClick"
|
||||
data-bs-target="#footerModal"
|
||||
data-test-id="funnel-footer-main-button"
|
||||
data-test-id="nav-bar-main-button"
|
||||
data-bs-dismiss="modal" />
|
||||
</div>
|
||||
<div v-if="!isBackButtonHidden" class="col-auto link-col py-1 text-break">
|
||||
|
|
@ -27,7 +29,7 @@
|
|||
data-bs-dismiss="modal" />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -35,12 +37,13 @@ import textLink from "@/ux-components/text-link/text-link";
|
|||
import buttonMain from "@/ux-components/button-main/button-main";
|
||||
|
||||
export default {
|
||||
name: "funnelFooter",
|
||||
name: "navbar",
|
||||
emits: ["BackClicked", "ForwardClicked"], // <--- should remove oodles of warnings in dev tools
|
||||
props: {
|
||||
isForwardActionDisabled: Boolean,
|
||||
isBackButtonHidden: { type: Boolean, default: false },
|
||||
cmsWidgetName: String,
|
||||
buttonSize: { type: Boolean, default: false },
|
||||
},
|
||||
components: {
|
||||
textLink,
|
||||
|
|
@ -90,9 +93,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.footer {
|
||||
.nav-bar {
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
@ -120,5 +124,17 @@ export default {
|
|||
& > .container-fluid {
|
||||
overflow-x: visible; // needed to fix hidden footer on some iphones
|
||||
}
|
||||
.medium-button {
|
||||
// Define styles for a medium button (default size)
|
||||
justify-content: flex-end;
|
||||
width: auto;
|
||||
}
|
||||
.large-button {
|
||||
justify-content: center;
|
||||
.btn-primary {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -12,8 +12,8 @@ export default {
|
|||
method,
|
||||
endpoint,
|
||||
payload,
|
||||
logApiCall = true,
|
||||
isFormData = false,
|
||||
logApiCall = false,
|
||||
pageNameToLog = null,
|
||||
additionalSuccessEventDataHandler,
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -37,13 +37,11 @@ export default {
|
|||
if (additionalSuccessEventDataHandler) {
|
||||
additionalEventData = "_" + additionalSuccessEventDataHandler(response);
|
||||
}
|
||||
const pageName = analyticsMixIn.methods.getPageName();
|
||||
const nextPageName = router.lastNavigationPage || pageName;
|
||||
const endpointWithoutParams =
|
||||
analyticsMixIn.methods.removeParamsFromEndpoint(endpoint);
|
||||
analyticsMixIn.methods.pushEventToGA(
|
||||
GaCategories.API_RESPONSE,
|
||||
`${nextPageName}_${endpointWithoutParams}`,
|
||||
`${pageNameToLog}_${endpointWithoutParams}`,
|
||||
`${GaLabels.SUCCESS}${additionalEventData}`,
|
||||
true
|
||||
);
|
||||
|
|
|
|||
|
|
@ -54,10 +54,11 @@ export function includesWindshieldReplacement() {
|
|||
return windshieldMatches.length > 0;
|
||||
}
|
||||
|
||||
export async function isGlassAvailableForCarId(carId) {
|
||||
const newGlassOptions = await baseMixin.methods.dispatchStoreAction(
|
||||
export async function isGlassAvailableForCarId(carId, pageNameToLog) {
|
||||
const newGlassOptions = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_DAMAGE_OPTIONS,
|
||||
{ carId: carId }
|
||||
{ carId: carId },
|
||||
pageNameToLog
|
||||
);
|
||||
|
||||
const currentGlassOptions = store.getters.damage.glassToReplace;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@ jest.mock("@/mixins/base-mixin.js", () => ({
|
|||
},
|
||||
};
|
||||
}),
|
||||
dispatchStoreActionWithLogging: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
data: {
|
||||
windshieldOptions: { availableReplacementOptions: ["windshield"] },
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
@ -77,7 +84,7 @@ describe("damage-helper.js", () => {
|
|||
];
|
||||
|
||||
// Act
|
||||
const isGlassAvailable = await isGlassAvailableForCarId();
|
||||
const isGlassAvailable = await isGlassAvailableForCarId("id", "testName");
|
||||
|
||||
// Assert
|
||||
expect(isGlassAvailable).toEqual(true);
|
||||
|
|
@ -91,7 +98,7 @@ describe("damage-helper.js", () => {
|
|||
{ glassLocation: "Windshield", glassName: "sideWindow" },
|
||||
];
|
||||
|
||||
const isGlassAvailable = await isGlassAvailableForCarId();
|
||||
const isGlassAvailable = await isGlassAvailableForCarId("id", "testName");
|
||||
|
||||
// Assert
|
||||
expect(isGlassAvailable).toEqual(false);
|
||||
|
|
|
|||
64
src/helpers/date-helper.js
Normal file
64
src/helpers/date-helper.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
export function getDateDifferenceInDays(startDate, endDate) {
|
||||
var date1 = new Date(endDate);
|
||||
date1.setHours(0, 0, 0, 0);
|
||||
var date2 = new Date(startDate);
|
||||
date2.setHours(0, 0, 0, 0);
|
||||
// To calculate the time difference of two dates
|
||||
var Difference_In_Time = date1.getTime() - date2.getTime();
|
||||
// To calculate the no. of days between two dates
|
||||
return Difference_In_Time / (1000 * 3600 * 24);
|
||||
}
|
||||
|
||||
export function getFullDayName(date) {
|
||||
if (date instanceof Date !== true) return;
|
||||
const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
return days[date.getDay()];
|
||||
}
|
||||
export function getFullMonthName(date) {
|
||||
if (date instanceof Date !== true) return;
|
||||
const monthNames = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
return monthNames[date.getMonth()];
|
||||
}
|
||||
export function get12HourTimeFormat(time) {
|
||||
// Check correct time format and split into components
|
||||
time = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time];
|
||||
|
||||
if (time.length > 1) {
|
||||
// If time format correct
|
||||
time = time.slice(1); // Remove full string match value
|
||||
time[5] = +time[0] < 12 ? " AM" : " PM"; // Set AM/PM
|
||||
time[0] = +time[0] % 12 || 12; // Adjust hours
|
||||
}
|
||||
return time.join(""); // return adjusted time or original string
|
||||
}
|
||||
export function get12HourTimeMobileFormat(time) {
|
||||
// Check correct time format and split into components
|
||||
time = time.toString().match(/^([01]\d|2[0-3])(:)([0-5]\d)?$/) || [time];
|
||||
|
||||
if (time.length > 1) {
|
||||
// If time format correct
|
||||
const min = time[3];
|
||||
time = time.slice(1); // Remove full string match value
|
||||
if (Number(min) == 0) {
|
||||
time = time.slice(0, 1); // Remove minute value
|
||||
time[1] = +time[0] < 12 ? " AM" : " PM"; // Set AM/PM
|
||||
} else {
|
||||
time[5] = +time[0] < 12 ? " AM" : " PM"; // Set AM/PM
|
||||
}
|
||||
time[0] = +time[0] % 12 || 12; // Adjust hours
|
||||
}
|
||||
return time.join(""); // return adjusted time or original string
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||
import { externalUrls } from "@/router/router-constants/externalUrl-values";
|
||||
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
|
||||
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
||||
|
|
@ -16,15 +17,11 @@ import router from "@/router";
|
|||
drop them so they don't start at the beginning again. This method will return 'heritage' if
|
||||
the user has an existing order and they come back in from the Safelite.com CTA.
|
||||
*/
|
||||
export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHeritageOrder = false) {
|
||||
export async function getPageToRouteExistingOrderTo(toRoute = {}) {
|
||||
// If the user is coming in via the Safelite.Com CTA
|
||||
if (toRoute.query[queryStrings.START_TYPE] === "fmg") {
|
||||
// If they have an existing order, return 'heritage' for the page name.
|
||||
if (existingHeritageOrder) {
|
||||
return fmgPageValues.HERITAGE;
|
||||
}
|
||||
|
||||
return await getLatestPageForRedirection();
|
||||
const latestPageRoute = await getLatestPageForRedirection();
|
||||
return latestPageRoute;
|
||||
}
|
||||
|
||||
// If navigating to a specific page, and that page is not part of the vin pages.
|
||||
|
|
@ -41,7 +38,6 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHerita
|
|||
// If this is not a direct link to a page using fmgPage, not from Safelite.com CTA or this is a vin related page.
|
||||
// Get the latest page for redirection.
|
||||
const latestPageRoute = await getLatestPageForRedirection();
|
||||
|
||||
return latestPageRoute;
|
||||
}
|
||||
|
||||
|
|
@ -49,31 +45,41 @@ export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHerita
|
|||
Used to navigate to the heritage funnel with the correct query string and url.
|
||||
*/
|
||||
|
||||
export async function navigateToHeritageFunnel({ shouldSaveSession = true, loadingModal }) {
|
||||
export async function navigateToHeritageFunnel({ shouldSaveSession, pageNameToLog, loadingModal }) {
|
||||
// Create the order (or save existing order) when navigating to Heritage Funnel.
|
||||
if (shouldSaveSession) {
|
||||
await saveSession({ shouldAwaitSaveSessionQueue: true });
|
||||
await saveSession({ pageNameToLog: pageNameToLog, shouldAwaitSaveSessionQueue: true });
|
||||
}
|
||||
|
||||
if (loadingModal && loadingModal.showModal) {
|
||||
loadingModal.showModal();
|
||||
}
|
||||
|
||||
router.navigateToExternalUrl(externalUrls.HERITAGE_FUNNEL, {
|
||||
var heritageParms = {
|
||||
corid: store.getters.order.referralCorrelationId,
|
||||
src: "concept-funnel",
|
||||
conceptsqid: store.getters.applicationUser.savedSessionId,
|
||||
isInsurance: store.getters.payment.isInsurance,
|
||||
});
|
||||
};
|
||||
|
||||
const promo = getQuerystringParameter(queryStrings.PROMO);
|
||||
|
||||
if (promo) {
|
||||
heritageParms["promo"] = promo;
|
||||
}
|
||||
|
||||
router.navigateToExternalUrl(externalUrls.HERITAGE_FUNNEL, heritageParms);
|
||||
}
|
||||
|
||||
export async function skipVinLookup() {
|
||||
if (store.getters.damage.isRepair) {
|
||||
return true;
|
||||
}
|
||||
const isVinOptionalVehicle = store.getters.order.vehicle.make
|
||||
? await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE)
|
||||
: false;
|
||||
|
||||
return (
|
||||
store.getters.damage.isRepair ||
|
||||
isVinOptionalVehicle ||
|
||||
!includesWindshieldReplacement() ||
|
||||
experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true")
|
||||
|
|
@ -81,18 +87,18 @@ export async function skipVinLookup() {
|
|||
}
|
||||
|
||||
export async function skipVinLookupNotRepair() {
|
||||
if (store.getters.damage.isRepair) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isVinOptionalVehicle = store.getters.order.vehicle.make
|
||||
? await store.dispatch(storeActions.IS_VIN_OPTIONAL_VEHICLE)
|
||||
: false;
|
||||
|
||||
return (
|
||||
!store.getters.damage.isRepair &&
|
||||
(isVinOptionalVehicle ||
|
||||
!includesWindshieldReplacement() ||
|
||||
experimentMixin.methods.hasSettingEqualTo(
|
||||
experimentSettings.SUPPRESS_VIN_CAPTURE,
|
||||
"true"
|
||||
))
|
||||
isVinOptionalVehicle ||
|
||||
!includesWindshieldReplacement() ||
|
||||
experimentMixin.methods.hasSettingEqualTo(experimentSettings.SUPPRESS_VIN_CAPTURE, "true")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE);
|
||||
|
|
@ -59,7 +59,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_DAMAGE);
|
||||
|
|
@ -97,7 +97,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.ESTIMATE);
|
||||
|
|
@ -125,7 +125,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.ESTIMATE);
|
||||
|
|
@ -153,7 +153,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.CAPABILITY_QUESTIONS);
|
||||
|
|
@ -181,7 +181,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.MOLDING_QUESTIONS);
|
||||
|
|
@ -209,7 +209,7 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_PARTS);
|
||||
|
|
@ -237,29 +237,11 @@ describe("getPageToRouteExistingOrderTo", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.PART_QUESTIONS);
|
||||
});
|
||||
|
||||
test("existing order > should return heritage", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {
|
||||
[queryStrings.START_TYPE]: "fmg",
|
||||
},
|
||||
};
|
||||
|
||||
store.commit(storeMutations.UPDATE_IS_REPAIR, false);
|
||||
store.commit(storeMutations.UPDATE_MAKE, "acura");
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, true);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(fmgPageValues.HERITAGE);
|
||||
});
|
||||
});
|
||||
|
||||
describe("navigateToHeritageFunnel", () => {
|
||||
|
|
@ -295,7 +277,7 @@ describe("navigateToHeritageFunnel", () => {
|
|||
router.navigateToExternalUrl = jest.fn();
|
||||
|
||||
// Act
|
||||
await navigateToHeritageFunnel({});
|
||||
await navigateToHeritageFunnel({ loadingModal: null, shouldSaveSession: true });
|
||||
|
||||
// Assert
|
||||
expect(saveSessionFunction).toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { storeMutations } from "@/constants/store-mutations";
|
|||
it will reset the state and go back to the start of the funnel.
|
||||
|
||||
*/
|
||||
export async function loadSessionIfPresent(isConceptInsurance) {
|
||||
export async function loadSessionIfPresent(isConceptInsurance, pageNameToLog) {
|
||||
const funnelCookie = getFunnelCookie();
|
||||
|
||||
// Do nothing if there is no cookie or session to use for loading.
|
||||
|
|
@ -42,7 +42,8 @@ export async function loadSessionIfPresent(isConceptInsurance) {
|
|||
funnelCookie.ReferralDate,
|
||||
funnelCookie.ReferralParentAccountNumber,
|
||||
funnelCookie.ReferralCorrelationId,
|
||||
isConceptInsurance
|
||||
isConceptInsurance,
|
||||
pageNameToLog
|
||||
)
|
||||
)?.data;
|
||||
}
|
||||
|
|
@ -52,17 +53,17 @@ export async function loadSessionIfPresent(isConceptInsurance) {
|
|||
This will also set Referral information in the store after saving, and then
|
||||
update the cookie. To force synchronous behavior pass in 'true' for shouldAwaitSaveSessionQueue
|
||||
*/
|
||||
export async function saveSession({ shouldAwaitSaveSessionQueue = false }) {
|
||||
export async function saveSession({ pageNameToLog, shouldAwaitSaveSessionQueue = false }) {
|
||||
var saveSessionPromise;
|
||||
if (store.getters.applicationUser.saveSessionPromise) {
|
||||
// queue newest request after current saveSessionPromise resolves
|
||||
saveSessionPromise = store.getters.applicationUser.saveSessionPromise.then(() => {
|
||||
// get a new saveSessionPromise
|
||||
return saveSessionHelper();
|
||||
return saveSessionHelper(pageNameToLog);
|
||||
});
|
||||
} else {
|
||||
// create an initial saveSessionPromise
|
||||
saveSessionPromise = saveSessionHelper();
|
||||
saveSessionPromise = saveSessionHelper(pageNameToLog);
|
||||
}
|
||||
store.commit(storeMutations.UPDATE_SAVE_SESSION_PROMISE, saveSessionPromise);
|
||||
// await here to allow for a caller to await and make the function synchronous
|
||||
|
|
@ -71,6 +72,13 @@ export async function saveSession({ shouldAwaitSaveSessionQueue = false }) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function submitWorkOrder({ pageNameToLog }) {
|
||||
await saveSession({
|
||||
pageNameToLog: pageNameToLog,
|
||||
shouldAwaitSaveSessionQueue: true,
|
||||
});
|
||||
}
|
||||
|
||||
// PRIVATE FUNCTIONS //
|
||||
|
||||
/*
|
||||
|
|
@ -83,12 +91,13 @@ async function loadSession(
|
|||
referralDate,
|
||||
parentAccountNumber,
|
||||
referralCorrelationId,
|
||||
isConceptInsurance
|
||||
isConceptInsurance,
|
||||
pageNameToLog
|
||||
) {
|
||||
// await the saveSessionPromise in the store to make sure we're loading up to date information
|
||||
await store.getters.applicationUser.saveSessionPromise;
|
||||
|
||||
const response = await baseMixin.methods.dispatchStoreAction(
|
||||
const response = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.LOAD_SESSION,
|
||||
{
|
||||
savedSessionId: savedSessionId?.toString(),
|
||||
|
|
@ -98,6 +107,7 @@ async function loadSession(
|
|||
parentAccountNumber,
|
||||
isConceptInsurance,
|
||||
},
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
|
||||
|
|
@ -107,8 +117,12 @@ async function loadSession(
|
|||
/*
|
||||
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
|
||||
*/
|
||||
async function saveSessionHelper() {
|
||||
const savedSessionInfo = await baseMixin.methods.dispatchStoreAction(storeActions.SAVE_SESSION);
|
||||
async function saveSessionHelper(pageNameToLog) {
|
||||
const savedSessionInfo = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.SAVE_SESSION,
|
||||
null,
|
||||
pageNameToLog
|
||||
);
|
||||
// Update the store with information received from the saveSession response
|
||||
await baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
|
||||
|
|
@ -121,6 +135,7 @@ async function saveSessionHelper() {
|
|||
savedSessionId: savedSessionInfo.data.savedSessionId,
|
||||
crmCustomerId: savedSessionInfo.data.crmCustomerId?.toString(),
|
||||
eon: savedSessionInfo.data.eon,
|
||||
workOrderNumber: savedSessionInfo.data.workOrderNumber,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import * as cookieHelper from "@/helpers/heritage-integration/cookie-helper";
|
||||
import { loadSessionIfPresent, saveSession } from "@/helpers/heritage-integration/order-helper";
|
||||
import {
|
||||
loadSessionIfPresent,
|
||||
saveSession,
|
||||
submitWorkOrder,
|
||||
} from "@/helpers/heritage-integration/order-helper";
|
||||
import { cookieNames } from "@/constants/cookie-names";
|
||||
import {
|
||||
setupMocksForJsFiles,
|
||||
|
|
@ -170,11 +174,13 @@ describe("saveSession", () => {
|
|||
const mocks = setupMocksForJsFiles(mockData);
|
||||
|
||||
// Act
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: "test" });
|
||||
|
||||
// Assert
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
storeActions.SAVE_SESSION
|
||||
expect(mocks.baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
|
||||
storeActions.SAVE_SESSION,
|
||||
null,
|
||||
"test"
|
||||
);
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
|
||||
|
|
@ -231,7 +237,115 @@ describe("saveSession", () => {
|
|||
setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
|
||||
|
||||
// Act
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: "test" });
|
||||
|
||||
// Assert
|
||||
expect(cookieHelper.getFunnelCookie().DidHeritageFunnelUpdateLast).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("submitWorkOrder", () => {
|
||||
afterEach(() => {
|
||||
removeAllTestCookies();
|
||||
});
|
||||
|
||||
test("submitWorkOrder => should set state order values", async () => {
|
||||
// Arrange
|
||||
const mockReferralNumber = "2";
|
||||
const mockCorrelationId = "55";
|
||||
const mockReferralDate = "2022";
|
||||
const mockParentAccountNumber = "167132";
|
||||
const mockSavedSessionId = "xxx-xxx-xxx";
|
||||
const mockCrmCustomerId = "xxx-xxx-xxx";
|
||||
|
||||
const mockOrderInfo = getMockOrderInfo(
|
||||
mockReferralNumber,
|
||||
mockCorrelationId,
|
||||
mockReferralDate,
|
||||
mockParentAccountNumber,
|
||||
mockSavedSessionId,
|
||||
mockCrmCustomerId
|
||||
);
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.SAVE_SESSION,
|
||||
data: mockOrderInfo,
|
||||
},
|
||||
{
|
||||
actionName: storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mocks = setupMocksForJsFiles(mockData);
|
||||
|
||||
// Act
|
||||
await submitWorkOrder({ pageNameToLog: "test" });
|
||||
|
||||
// Assert
|
||||
expect(mocks.baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
|
||||
storeActions.SAVE_SESSION,
|
||||
null,
|
||||
"test"
|
||||
);
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
|
||||
{
|
||||
referralNumber: mockReferralNumber,
|
||||
referralDate: mockReferralDate,
|
||||
referralCorrelationId: mockCorrelationId,
|
||||
parentAccountNumber: mockParentAccountNumber,
|
||||
savedSessionId: mockSavedSessionId,
|
||||
crmCustomerId: mockCrmCustomerId,
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("submitWorkOrder => should update DidHeritageFunnelUpdateLast cookie value to false", async () => {
|
||||
// Arrange
|
||||
const mockReferralNumber = 1566818;
|
||||
const mockReferralDate = "2022-03-15T10:56:24.597";
|
||||
const mockReferralCorrelationId = "404d2b04-f86e-45c3-b373-127b6217b060";
|
||||
const mockParentAccountNumber = "167132";
|
||||
const mockSavedSessionId = "xxx-xxx-xxx";
|
||||
const mockCrmCustomerId = "xxx-xxx-xxx";
|
||||
|
||||
const mockOrderInfo = getMockOrderInfo(
|
||||
mockReferralNumber,
|
||||
mockReferralCorrelationId,
|
||||
mockReferralDate,
|
||||
mockParentAccountNumber,
|
||||
mockSavedSessionId,
|
||||
mockCrmCustomerId
|
||||
);
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.SAVE_SESSION,
|
||||
data: mockOrderInfo,
|
||||
},
|
||||
],
|
||||
router: router,
|
||||
};
|
||||
|
||||
setupMocksForJsFiles(mockData);
|
||||
|
||||
const testCookieValue = {
|
||||
ReferralNumber: mockReferralNumber,
|
||||
ReferralDate: mockReferralDate,
|
||||
ReferralCorrelationId: mockReferralCorrelationId,
|
||||
ShouldResetState: false,
|
||||
DidHeritageFunnelUpdateLast: true,
|
||||
};
|
||||
|
||||
setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
|
||||
|
||||
// Act
|
||||
await submitWorkOrder({ pageNameToLog: "test" });
|
||||
|
||||
// Assert
|
||||
expect(cookieHelper.getFunnelCookie().DidHeritageFunnelUpdateLast).toEqual(false);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ export function findLineItemsWithPartType(typeToFind, itemsToSearch) {
|
|||
const partTypeMatches = itemsToSearch?.filter(
|
||||
(lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase()
|
||||
);
|
||||
|
||||
return partTypeMatches;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,16 @@ export function getMountOptions(mockData) {
|
|||
});
|
||||
}
|
||||
});
|
||||
mocks.dispatchStoreActionWithLogging = jest.fn();
|
||||
mocks.dispatchStoreActionWithLogging.mockImplementation((actionName) => {
|
||||
let actionFilterResult = mockData.actionList?.filter((x) => x.actionName == actionName);
|
||||
|
||||
if (actionFilterResult?.length === 1) {
|
||||
return Promise.resolve({
|
||||
data: actionFilterResult[0].data,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Mock const files
|
||||
mocks.storeActions = storeActions;
|
||||
|
|
@ -141,5 +151,16 @@ function setupBaseMixinDispatchStoreAction(mockData) {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
baseMixin.methods.dispatchStoreActionWithLogging = jest.fn();
|
||||
baseMixin.methods.dispatchStoreActionWithLogging.mockImplementation((actionName) => {
|
||||
let actionFilterResult = mockData.actionList.filter((x) => x.actionName == actionName);
|
||||
|
||||
if (actionFilterResult.length > 0 && actionFilterResult.length === 1) {
|
||||
return Promise.resolve({
|
||||
data: actionFilterResult[0].data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ describe("address-lookup.vue", () => {
|
|||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
expect(wrapper.vm.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
|
||||
"lookupVinByAddress",
|
||||
{
|
||||
licenseLastName: undefined,
|
||||
|
|
@ -479,12 +479,17 @@ describe("address-lookup.vue", () => {
|
|||
licenseStreetAddress: "1234 Main St",
|
||||
licenseZip: "43215",
|
||||
},
|
||||
"address-lookup",
|
||||
false
|
||||
);
|
||||
|
||||
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith("validateZip", {
|
||||
zip: "43215",
|
||||
});
|
||||
expect(wrapper.vm.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
|
||||
"validateZip",
|
||||
{
|
||||
zip: "43215",
|
||||
},
|
||||
"address-lookup"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -722,8 +727,8 @@ function setupMocks({
|
|||
|
||||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
|
||||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,75 +5,92 @@
|
|||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
autocomplete="off">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
ref="vehicleBanner"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
|
||||
<alert
|
||||
ref="alertVinNotFound"
|
||||
v-if="displayVinNotFoundAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertVinNotFoundWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertMatchedDifferentVehicle"
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
class="mb-4"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
alertClass="alert-warning"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertNonServiceableZip"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
class="mb-4"
|
||||
alertClass="alert-danger"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
ref="alertVinLookupsByHomeAddressNotAllowed"
|
||||
v-if="displayVinLookupByHomeAddressNotAllowedAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertVinLookupsByHomeAddressNotAllowedWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="service-zip-field" v-if="showServiceZipField" aria-live="polite">
|
||||
<div class="row mt-4 mb-1">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
ref="serviceZip"
|
||||
customInputId="serviceZip"
|
||||
aria-haspopup=""
|
||||
mask="#####"
|
||||
validationRules="service-zip-required|service-zip-format" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
ref="vehicleBanner"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" ref="funnelSubHeader" />
|
||||
|
||||
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
|
||||
|
||||
<alert
|
||||
ref="alertVinNotFound"
|
||||
v-if="displayVinNotFoundAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertVinNotFoundWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
ref="alertMatchedDifferentVehicle"
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
class="mb-4"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
alertClass="alert-warning"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
ref="alertNonServiceableZip"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
class="mb-4"
|
||||
alertClass="alert-danger"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
ref="alertVinLookupsByHomeAddressNotAllowed"
|
||||
v-if="displayVinLookupByHomeAddressNotAllowedAlert"
|
||||
class="mb-4"
|
||||
cmsWidgetName="AlertVinLookupsByHomeAddressNotAllowedWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<transition name="fade" mode="out-in">
|
||||
<div
|
||||
class="service-zip-field"
|
||||
v-if="showServiceZipField"
|
||||
aria-live="polite">
|
||||
<div class="row mt-4 mb-1">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
ref="serviceZip"
|
||||
customInputId="serviceZip"
|
||||
aria-haspopup=""
|
||||
mask="#####"
|
||||
validationRules="service-zip-required|service-zip-format" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isDisabled="!meta.valid"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
@back-clicked="backButtonAction"
|
||||
:isForwardActionDisabled="!meta.valid" />
|
||||
</transition>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isDisabled="!meta.valid"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
@back-clicked="backButtonAction"
|
||||
:isForwardActionDisabled="!meta.valid" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -82,7 +99,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import customerQuestions from "@/layouts/address-lookup/customer-questions/customer-questions";
|
||||
|
|
@ -207,7 +224,7 @@ export default {
|
|||
// Vehicle info change in the flow, use a variable to keep track and commit to state at the end.
|
||||
let vehicleInfoToCommit = {};
|
||||
|
||||
const vinLookupResponse = this.dispatchStoreAction(
|
||||
const vinLookupResponse = this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VIN_BY_ADDRESS,
|
||||
{
|
||||
licenseLastName: this.customerQuestions.lastName,
|
||||
|
|
@ -215,6 +232,7 @@ export default {
|
|||
licenseZip: this.customerQuestions.addressQuestions.zipCode,
|
||||
licenseState: this.customerQuestions.addressQuestions.state,
|
||||
},
|
||||
"address-lookup",
|
||||
false
|
||||
);
|
||||
|
||||
|
|
@ -227,12 +245,20 @@ export default {
|
|||
{
|
||||
resultKey: "serviceZipValidationResponse",
|
||||
promise: this.serviceZipCode
|
||||
? this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
|
||||
zip: this.serviceZipCode,
|
||||
})
|
||||
: this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
|
||||
zip: this.customerQuestions.addressQuestions.zipCode,
|
||||
}),
|
||||
? this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{
|
||||
zip: this.serviceZipCode,
|
||||
},
|
||||
"address-lookup"
|
||||
)
|
||||
: this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{
|
||||
zip: this.customerQuestions.addressQuestions.zipCode,
|
||||
},
|
||||
"address-lookup"
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -241,14 +267,14 @@ export default {
|
|||
if (!resultMap.vinLookupResponse.isStatePermissible) {
|
||||
// State Restrictions forbid lookup by address
|
||||
this.displayVinLookupByHomeAddressNotAllowedAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If a Service Zip is entered and it is an invalid zip code (ex. 11111) then show an alert
|
||||
const isZipValid = resultMap.serviceZipValidationResponse.isValid;
|
||||
if (this.serviceZipCode && !isZipValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
|
||||
|
|
@ -267,14 +293,15 @@ export default {
|
|||
this.displayMatchedDifferentVehicleAlert = true;
|
||||
|
||||
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
||||
carFound.carId
|
||||
carFound.carId,
|
||||
"address-lookup"
|
||||
);
|
||||
|
||||
// Update button "Continue with..."
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
`Continue with ${carFound.year} ${carFound.make} ${carFound.model}`
|
||||
);
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// update data if the zip or service zip is serviceable
|
||||
|
|
@ -294,7 +321,7 @@ export default {
|
|||
} else {
|
||||
// No VINS found.
|
||||
this.displayVinNotFoundAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If the either the registration zip code or service zip code are not serviceable
|
||||
|
|
@ -302,7 +329,7 @@ export default {
|
|||
if (!this.isZipServiceable) {
|
||||
this.displayNonServiceableZipAlert = true;
|
||||
this.showServiceZipField = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If the registration zip code is serviceable and nothing was entered for the service zip code
|
||||
|
|
@ -320,7 +347,7 @@ export default {
|
|||
Object.keys(vehicleInfoToCommit).length === 0
|
||||
? this.$store.getters.vehicle
|
||||
: vehicleInfoToCommit,
|
||||
registrationInfo: {
|
||||
customerInfo: {
|
||||
firstName: this.customerQuestions.firstName,
|
||||
lastName: this.customerQuestions.lastName,
|
||||
},
|
||||
|
|
@ -420,7 +447,7 @@ export default {
|
|||
customerQuestions: {
|
||||
handler(newValue) {
|
||||
// if they modify one of the lookup fields (address, city, state, zipCode, or lastName), then modify the button text back to "Get my personalized quote"
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
this.showServiceZipField = false;
|
||||
|
|
@ -445,7 +472,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
customerQuestions,
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@ describe("addressVehicles.vue", () => {
|
|||
};
|
||||
|
||||
// the following has to be set BEFORE changing the data which is being watched, and requires updateButtonText to be mocked
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
wrapper.vm.lookupVin = jest.fn(() => Promise.resolve(lookupVinResponse));
|
||||
wrapper.vm.$router.navigate = jest.fn();
|
||||
wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(() => {});
|
||||
|
|
@ -106,8 +106,8 @@ describe("addressVehicles.vue", () => {
|
|||
};
|
||||
|
||||
// the following has to be set BEFORE changing the data which is being watched, and requires updateButtonText to be mocked
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
wrapper.vm.lookupVin = jest.fn(() => Promise.reject(lookupVinResponse));
|
||||
wrapper.vm.$router.navigateWithSaving = jest.fn();
|
||||
wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(() => {});
|
||||
|
|
@ -128,7 +128,7 @@ describe("addressVehicles.vue", () => {
|
|||
test("Should navigate to CLICKED_FORWARD scenario if carId is different and selected glass not available for vehicle on navigateForward", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$router.navigateWithSaving = jest.fn();
|
||||
|
||||
// Act
|
||||
|
|
@ -148,7 +148,7 @@ describe("addressVehicles.vue", () => {
|
|||
test("carId is not different on navigateForward (car was found) => Should handle navigating forward with car match", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
navigateToHeritage.navigateToHeritageFunnel = jest.fn();
|
||||
|
||||
// Act
|
||||
|
|
@ -224,7 +224,7 @@ function setupMocks({}) {
|
|||
|
||||
const wrapper = shallowMount(addressVehicles, mountOptions);
|
||||
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.navigateForwardWithSingleCarMatch = jest.fn();
|
||||
|
||||
return { wrapper };
|
||||
|
|
|
|||
|
|
@ -1,38 +1,51 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<alert
|
||||
ref="alertFoundMultipleVehicles"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="AlertFoundMultipleVehiclesHeader"
|
||||
manualCopy=""
|
||||
v-bind:isDismissible="false" />
|
||||
<addressVehiclesQuestion
|
||||
ref="addressVehiclesQuestion"
|
||||
cmsWidgetName="VehicleConfirmationQuestion"
|
||||
:vehicles="VehiclesForQuestions"
|
||||
validationRules="vehicle-required"
|
||||
v-model="selectedVehicleVin"
|
||||
:isCarIdDifferent="isCarIdDifferent" />
|
||||
<div class="alert-provide-vin" v-if="splitAlertProvideVinBodyForLink.length">
|
||||
<textBlock
|
||||
:customText="AlertProvideVinBody"
|
||||
justifyText="left"
|
||||
class="mb-3"
|
||||
marginTopSizeOverride="3" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<alert
|
||||
ref="alertFoundMultipleVehicles"
|
||||
class="my-5"
|
||||
alertClass="alert-warning"
|
||||
:manualHeadline="AlertFoundMultipleVehiclesHeader"
|
||||
manualCopy=""
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<addressVehiclesQuestion
|
||||
ref="addressVehiclesQuestion"
|
||||
cmsWidgetName="VehicleConfirmationQuestion"
|
||||
:vehicles="VehiclesForQuestions"
|
||||
validationRules="vehicle-required"
|
||||
v-model="selectedVehicleVin"
|
||||
:isCarIdDifferent="isCarIdDifferent" />
|
||||
|
||||
<div class="alert-provide-vin" v-if="splitAlertProvideVinBodyForLink.length">
|
||||
<textBlock
|
||||
:customText="AlertProvideVinBody"
|
||||
justifyText="left"
|
||||
class="mb-3"
|
||||
marginTopSizeOverride="3" />
|
||||
</div>
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
|
@ -40,7 +53,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
|
@ -162,10 +175,14 @@ export default {
|
|||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
const vinLookup = await this.dispatchStoreAction(storeActions.LOOKUP_VEHICLE_BY_VIN, {
|
||||
vin: this.selectedVehicle.vin,
|
||||
}).catch(() => {
|
||||
this.$refs.funnelFooter.removeLoader();
|
||||
const vinLookup = await this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VEHICLE_BY_VIN,
|
||||
{
|
||||
vin: this.selectedVehicle.vin,
|
||||
},
|
||||
"address-vehicles"
|
||||
).catch(() => {
|
||||
this.$refs.navbar.removeLoader();
|
||||
});
|
||||
|
||||
if (!vinLookup) {
|
||||
|
|
@ -173,7 +190,8 @@ export default {
|
|||
}
|
||||
|
||||
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
||||
vinLookup.data.carId
|
||||
vinLookup.data.carId,
|
||||
"address-vehicles"
|
||||
);
|
||||
|
||||
await this.dispatchStoreAction(
|
||||
|
|
@ -210,11 +228,11 @@ export default {
|
|||
this.isCarIdDifferent =
|
||||
this.selectedVehicle?.vehicle.carId !== store.getters.vehicle.carId;
|
||||
if (this.isCarIdDifferent) {
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
`Continue with ${this.selectedVehicle.vehicle.year} ${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model}`
|
||||
);
|
||||
} else {
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
}
|
||||
|
|
@ -229,7 +247,7 @@ export default {
|
|||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
alert,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
addressVehiclesQuestion,
|
||||
textBlock,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -237,6 +237,11 @@ describe("capabilityQuestions.vue", () => {
|
|||
data: [],
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
|
@ -272,6 +277,11 @@ describe("capabilityQuestions.vue", () => {
|
|||
data: [],
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
|
||||
|
||||
// Act
|
||||
|
|
@ -325,7 +335,12 @@ describe("capabilityQuestions.vue", () => {
|
|||
data: [],
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreActionWithLogging");
|
||||
|
||||
// Act
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
|
@ -334,9 +349,10 @@ describe("capabilityQuestions.vue", () => {
|
|||
|
||||
//Assert
|
||||
expect(spy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
1,
|
||||
"getPartFromCapabilityQuestionAnswer",
|
||||
"Windshield",
|
||||
"capability-questions",
|
||||
false
|
||||
);
|
||||
|
||||
|
|
@ -368,6 +384,11 @@ describe("capabilityQuestions.vue", () => {
|
|||
data: [],
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: [],
|
||||
};
|
||||
});
|
||||
wrapper.vm.navigateForward = jest.fn();
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -162,9 +162,10 @@ export default {
|
|||
const partsOrQuestions = this.partsOrQuestionsData;
|
||||
for (let answer of questionAnswersArray) {
|
||||
const partFromCapabilityQuestionAnswer = (
|
||||
await this.dispatchStoreAction(
|
||||
await this.dispatchStoreActionWithLogging(
|
||||
this.storeActions.GET_PART_FROM_CAPABILITY_QUESTION_ANSWER,
|
||||
answer.glassLocation,
|
||||
"capability-questions",
|
||||
false
|
||||
)
|
||||
).data;
|
||||
|
|
|
|||
133
src/layouts/confirmation/confirmation.spec.js
Normal file
133
src/layouts/confirmation/confirmation.spec.js
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// Components
|
||||
import confirmation from "@/layouts/confirmation/confirmation.vue";
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { nextTick } from "vue";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
store.getters = {
|
||||
order: {
|
||||
schedule: {
|
||||
date: "2019-01-01",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
routeCode: "000",
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: [
|
||||
{
|
||||
partNumber: "ABC123",
|
||||
},
|
||||
],
|
||||
supportingItems: [],
|
||||
},
|
||||
serviceLocation: {
|
||||
address: "",
|
||||
address2: null,
|
||||
city: "",
|
||||
state: "AZ",
|
||||
appointmentType: "Inshop",
|
||||
zipCode: "12345",
|
||||
zipCodeCtu: "01234",
|
||||
provider: {
|
||||
providerNumber: "123",
|
||||
address: {
|
||||
streetAddress: "test1",
|
||||
city: "test",
|
||||
state: "AZ",
|
||||
zipCode: "12345",
|
||||
zipCodeCtu: "01234",
|
||||
},
|
||||
},
|
||||
},
|
||||
damage: {
|
||||
isRepair: false,
|
||||
},
|
||||
referralNumber: "1234567",
|
||||
vehicle: {
|
||||
year: "2004",
|
||||
make: "Ford",
|
||||
model: "F Series F250",
|
||||
},
|
||||
customer: {
|
||||
emailAddress: "test@test.com",
|
||||
},
|
||||
},
|
||||
payment: {
|
||||
isInsurance: true,
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: [],
|
||||
supportingItems: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
afterEach(() => {
|
||||
store.getters = {};
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("confirmation.vue", () => {
|
||||
const realLocation = window.location;
|
||||
|
||||
beforeAll(() => {
|
||||
delete window.location;
|
||||
window.location = { ...realLocation, assign: jest.fn() };
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
window.location = realLocation;
|
||||
});
|
||||
test("arePagePrerequisitesValid should be true ", async () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
await nextTick();
|
||||
|
||||
//Assert
|
||||
expect(arePagePrerequisitesValid).toBe(true);
|
||||
});
|
||||
test("'Back to safelite.com' button should take user back to safelite.com", async () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
//Assert
|
||||
expect(window.location.assign).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
const wordingText = "wording Text";
|
||||
function setupMocks({ customMountOptions }) {
|
||||
const mountOptions = getMountOptions({
|
||||
...customMountOptions,
|
||||
});
|
||||
|
||||
mountOptions.mixins = [
|
||||
{
|
||||
methods: {
|
||||
getCmsContent: jest.fn().mockImplementation(() => {
|
||||
return wordingText;
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
const wrapper = shallowMount(confirmation, mountOptions);
|
||||
return { wrapper };
|
||||
}
|
||||
332
src/layouts/confirmation/confirmation.vue
Normal file
332
src/layouts/confirmation/confirmation.vue
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
<template>
|
||||
<div class="page-container-grouped-styles position-relative">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<div>{{ workOrderNumber }}</div>
|
||||
<div>
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="header-container">
|
||||
<img :src="ScheduleConfirmationImage" />
|
||||
<span v-html="ScheduleConfirmationText"></span>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div>
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
</div>
|
||||
<div class="scheduleText">
|
||||
<p>{{ this.getScheduleDate() }}</p>
|
||||
<p>{{ this.getScheduleTime() }}</p>
|
||||
</div>
|
||||
|
||||
<div class="calendar-section">
|
||||
<div class="add-to-calendar">
|
||||
<button>
|
||||
<span class="add-to-calendar-span">
|
||||
<img
|
||||
src="~@/assets/img/icons/add-to-calendar.svg"
|
||||
class="add-to-calendar-img" />
|
||||
<span class="add-to-calendar-text"
|
||||
>Add to calendar</span
|
||||
></span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="appoinmenttext"
|
||||
v-html="this.getAppointmentWordingText()"></div>
|
||||
</div>
|
||||
<div class="emailtext" v-html="this.getConfirmationEmailText()"></div>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
isBackButtonHidden="true"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
:buttonSize="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
//Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { applicationConfig } from "@/constants/application-config.js";
|
||||
import store from "@/store";
|
||||
|
||||
import {
|
||||
getFullDayName,
|
||||
getFullMonthName,
|
||||
get12HourTimeFormat,
|
||||
get12HourTimeMobileFormat,
|
||||
} from "@/helpers/date-helper";
|
||||
export default {
|
||||
name: "confirmation",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
ScheduleConfirmationText() {
|
||||
return this.getCmsContent("ScheduleConfirmationWidget", "BodyText");
|
||||
},
|
||||
ScheduleConfirmationImage() {
|
||||
return this.getCmsContent("ScheduleConfirmationWidget", "Image");
|
||||
},
|
||||
ConfirmationEmailText() {
|
||||
return this.getCmsContent("ConfirmationEmailWidget", "BodyText");
|
||||
},
|
||||
ScheduleDate() {
|
||||
return store.getters.order.schedule.date;
|
||||
},
|
||||
AppointmentType() {
|
||||
return store.getters.order.serviceLocation.appointmentType;
|
||||
},
|
||||
ScheduleStartTime() {
|
||||
return store.getters.order.schedule.startTime;
|
||||
},
|
||||
ScheduleEndTime() {
|
||||
return store.getters.order.schedule.endTime;
|
||||
},
|
||||
InShopWordingText() {
|
||||
return this.getCmsContent("InShopWordingWidget", "BodyText");
|
||||
},
|
||||
MobileWordingText() {
|
||||
return this.getCmsContent("MobileWordingWidget", "BodyText");
|
||||
},
|
||||
DropOffWordingText() {
|
||||
return this.getCmsContent("DropOffWordingWidget", "BodyText");
|
||||
},
|
||||
ServiceLocationAddress() {
|
||||
return store.getters.order.serviceLocation.address;
|
||||
},
|
||||
ServiceLocationAddress2() {
|
||||
return store.getters.order.serviceLocation.address2;
|
||||
},
|
||||
ServiceLocationCity() {
|
||||
return store.getters.order.serviceLocation.city;
|
||||
},
|
||||
ServiceLocationState() {
|
||||
return store.getters.order.serviceLocation.state;
|
||||
},
|
||||
ServiceLocationZipCode() {
|
||||
return store.getters.order.serviceLocation.zipCode;
|
||||
},
|
||||
ProviderAddress() {
|
||||
return store.getters.order.serviceLocation.provider.address.streetAddress;
|
||||
},
|
||||
ProviderCity() {
|
||||
return store.getters.order.serviceLocation.provider.address.city;
|
||||
},
|
||||
ProviderState() {
|
||||
return store.getters.order.serviceLocation.provider.address.state;
|
||||
},
|
||||
ProviderZipCode() {
|
||||
return store.getters.order.serviceLocation.provider.address.zipCode;
|
||||
},
|
||||
Year() {
|
||||
return store.getters.order.vehicle.year;
|
||||
},
|
||||
Make() {
|
||||
return store.getters.order.vehicle.make;
|
||||
},
|
||||
Model() {
|
||||
return store.getters.order.vehicle.model;
|
||||
},
|
||||
Email() {
|
||||
return store.getters.order.customer.emailAddress;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
workOrderNumber: store.getters.order.workOrderNumber,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// Service Location
|
||||
const serviceLocation = store.getters.order.serviceLocation;
|
||||
const mobileReqs = !!(
|
||||
serviceLocation.address &&
|
||||
serviceLocation.city &&
|
||||
serviceLocation.state &&
|
||||
serviceLocation.zipCode
|
||||
);
|
||||
|
||||
const providerLocation = serviceLocation.provider.address;
|
||||
const dropOffInshopReqs = !!(
|
||||
providerLocation.streetAddress &&
|
||||
providerLocation.city &&
|
||||
providerLocation.state &&
|
||||
providerLocation.zipCode
|
||||
);
|
||||
|
||||
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
|
||||
|
||||
const serviceLocationReqs =
|
||||
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
|
||||
|
||||
// Schedule
|
||||
const schedule = store.getters.order.schedule;
|
||||
const scheduleReqs = !!(schedule.date && schedule.startTime); //removing schedule.endTime for testing
|
||||
|
||||
return serviceLocationReqs && scheduleReqs;
|
||||
},
|
||||
forwardButtonAction() {
|
||||
window.location.assign("//www.safelite.com/");
|
||||
},
|
||||
getScheduleDate() {
|
||||
const scheduleDate = new Date(this.ScheduleDate);
|
||||
return `${getFullDayName(scheduleDate)}, ${getFullMonthName(
|
||||
scheduleDate
|
||||
)} ${scheduleDate.getDate()}`;
|
||||
},
|
||||
getScheduleTime() {
|
||||
if (this.AppointmentType == AppointmentTypeStrings.MOBILE) {
|
||||
return `Between ${get12HourTimeMobileFormat(
|
||||
this.ScheduleStartTime
|
||||
)} - ${get12HourTimeMobileFormat(this.ScheduleEndTime)}`;
|
||||
} else if (this.AppointmentType == AppointmentTypeStrings.DROP_OFF) {
|
||||
return `Drop off before 9:30 AM`;
|
||||
} else {
|
||||
return `at ${get12HourTimeFormat(this.ScheduleStartTime)}`;
|
||||
}
|
||||
},
|
||||
|
||||
getAppointmentWordingText() {
|
||||
const ymm = `${this.Year} ${this.Make} ${this.Model}`;
|
||||
if (this.AppointmentType == AppointmentTypeStrings.MOBILE) {
|
||||
const address = `${
|
||||
this.ServiceLocationAddress2 ? this.ServiceLocationAddress2 + "," : ""
|
||||
} ${this.ServiceLocationAddress},<br/> ${this.ServiceLocationCity}, ${
|
||||
this.ServiceLocationState
|
||||
} ${this.ServiceLocationZipCode}`;
|
||||
return this.MobileWordingText.replace("#ADDRESS", address).replace("#YMM", ymm);
|
||||
} else if (this.AppointmentType == AppointmentTypeStrings.DROP_OFF) {
|
||||
const address = `${this.ProviderAddress},<br/> ${this.ProviderCity}, ${this.ProviderState} ${this.ProviderZipCode}`;
|
||||
return this.DropOffWordingText.replace("#ADDRESS", address).replace("#YMM", ymm);
|
||||
} else {
|
||||
const address = `${this.ProviderAddress},<br/> ${this.ProviderCity}, ${this.ProviderState} ${this.ProviderZipCode}`;
|
||||
return this.InShopWordingText.replace("#ADDRESS", address).replace("#YMM", ymm);
|
||||
}
|
||||
},
|
||||
getConfirmationEmailText() {
|
||||
return this.ConfirmationEmailText.replace("#EMAIL", this.Email)
|
||||
.replace("#MY_ACCOUNT_URL", applicationConfig.MY_ACCOUNT)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.main {
|
||||
padding: 1rem 0;
|
||||
box-shadow: 0 3px 10px rgb(0 0 0 / 0.2);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.scheduleText {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.scheduleText P {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
color: $black;
|
||||
|
||||
+ p {
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.calendar-section[data-v-8004c52c] {
|
||||
display: table;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.calendar-section .add-to-calendar {
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.add-to-calendar button {
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.calendar-section .add-to-calendar .add-to-calendar-span {
|
||||
cursor: pointer;
|
||||
}
|
||||
.calendar-section .add-to-calendar .add-to-calendar-img {
|
||||
display: inline;
|
||||
width: 20px;
|
||||
}
|
||||
.add-to-calendar-text {
|
||||
margin: 0 0 0 7px;
|
||||
color: #1574a1;
|
||||
vertical-align: middle;
|
||||
font-weight: 500;
|
||||
font-family: "Roboto";
|
||||
text-decoration: underline;
|
||||
}
|
||||
.header-container {
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
font-size: 1.25rem;
|
||||
padding: 0.3rem;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.appoinmenttext strong {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.emailtext {
|
||||
padding: 1.5rem 0 0;
|
||||
|
||||
p {
|
||||
font-size: 0.875rem;
|
||||
margin: 0;
|
||||
|
||||
strong {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,57 +1,72 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="my-5" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="FirstNameWidget"
|
||||
v-model="firstName"
|
||||
ref="firstName"
|
||||
customInputId="firstName"
|
||||
validationRules="first-name-required" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="LastNameWidget"
|
||||
v-model="lastName"
|
||||
ref="lastName"
|
||||
customInputId="lastName"
|
||||
validationRules="last-name-required" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="emailQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="email"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
<phoneNumberQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="PhoneNumberQuestionWidget"
|
||||
v-model="phoneNumber"
|
||||
isRequired
|
||||
validationRules="phone-number-required" />
|
||||
<checkboxQuestion
|
||||
class="mb-5"
|
||||
cmsWidgetName="TextMeQuestionWidget"
|
||||
v-model="isSmsOptIn" />
|
||||
<textareaQuestion
|
||||
class="mb-4"
|
||||
v-model="techNotes"
|
||||
cmsWidgetName="TextAreaContentWidget"
|
||||
maxLength="250" />
|
||||
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="my-5" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="FirstNameWidget"
|
||||
v-model="firstName"
|
||||
ref="firstName"
|
||||
customInputId="firstName"
|
||||
validationRules="first-name-required" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="LastNameWidget"
|
||||
v-model="lastName"
|
||||
ref="lastName"
|
||||
customInputId="lastName"
|
||||
validationRules="last-name-required" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="emailQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="email"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
|
||||
<phoneNumberQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="PhoneNumberQuestionWidget"
|
||||
v-model="phoneNumber"
|
||||
isRequired
|
||||
validationRules="phone-number-required" />
|
||||
|
||||
<checkboxQuestion
|
||||
class="mb-5"
|
||||
cmsWidgetName="TextMeQuestionWidget"
|
||||
v-model="isSmsOptIn" />
|
||||
|
||||
<textareaQuestion
|
||||
class="mb-4"
|
||||
v-model="techNotes"
|
||||
cmsWidgetName="TextAreaContentWidget"
|
||||
maxLength="250" />
|
||||
|
||||
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import textareaQuestion from "@/digital-components/textarea-question/textarea-question";
|
||||
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
|
||||
|
|
@ -67,7 +82,6 @@ import { routerParams } from "@/router/router-constants/router-params";
|
|||
import { required, regex } from "@/helpers/validation-rules";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { useField, validate } from "vee-validate";
|
||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||
import store from "@/store";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
|
|
@ -163,7 +177,6 @@ export default {
|
|||
false
|
||||
);
|
||||
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_WORK_ORDER_FLAG, true, false);
|
||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||
},
|
||||
},
|
||||
|
|
@ -172,7 +185,7 @@ export default {
|
|||
funnelSubHeader,
|
||||
textareaQuestion,
|
||||
textboxQuestion,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
Form,
|
||||
textBlock,
|
||||
phoneNumberQuestion,
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ function setupMocks({
|
|||
{ Name: "Provide my license plate # Most accurate VIN match" },
|
||||
{ Name: "Provide my home address Most convenient VIN match" },
|
||||
],
|
||||
funnelFooterWidget = { ForwardButtonText: "test txt" },
|
||||
FunnelFooterWidget = { ForwardButtonText: "test txt" },
|
||||
mountOptionsMockData = {
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
|
|
@ -271,7 +271,7 @@ function setupMocks({
|
|||
groupName: groupName,
|
||||
QuestionText: cmsQuestionText,
|
||||
Answers: cmsAnswers,
|
||||
FunnelFooterWidget: funnelFooterWidget,
|
||||
FunnelFooterWidget: FunnelFooterWidget,
|
||||
};
|
||||
|
||||
const apiPromise = Promise.resolve({ cmsContent });
|
||||
|
|
|
|||
|
|
@ -1,79 +1,84 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" />
|
||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<div v-if="!skipVin">
|
||||
<div class="vinlookupquestion" v-html="this.VinLookupQuestionText"></div>
|
||||
<buttonQuestion
|
||||
cmsWidgetName="VinLookupMethod"
|
||||
:answers="answersFromCms"
|
||||
groupName="vinLookupMethodOption"
|
||||
buttonTypeString="listButton"
|
||||
v-model="selectedVinLookupMethod"
|
||||
isRequired
|
||||
validationRules="option-required"
|
||||
:logDisplayedValuesEvent="true" />
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<alert
|
||||
class="my-4"
|
||||
:cmsWidgetName="alertInfo"
|
||||
alertClass="alert-info"
|
||||
v-bind:isDismissible="false" />
|
||||
<div class="row my-2">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
inputId="serviceZipCode"
|
||||
mask="#####"
|
||||
isRequired
|
||||
validationRules="zip-required|zip-format" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" />
|
||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div v-if="!skipVin">
|
||||
<div class="vinlookupquestion" v-html="this.VinLookupQuestionText"></div>
|
||||
<buttonQuestion
|
||||
cmsWidgetName="VinLookupMethod"
|
||||
:answers="answersFromCms"
|
||||
groupName="vinLookupMethodOption"
|
||||
buttonTypeString="listButton"
|
||||
v-model="selectedVinLookupMethod"
|
||||
isRequired
|
||||
validationRules="option-required"
|
||||
:logDisplayedValuesEvent="true" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="emailAddress"
|
||||
isRequired
|
||||
disableAutoFill
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<alert
|
||||
class="my-4"
|
||||
:cmsWidgetName="alertInfo"
|
||||
alertClass="alert-info"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
inputId="serviceZipCode"
|
||||
mask="#####"
|
||||
isRequired
|
||||
validationRules="zip-required|zip-format" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
inputId="emailAddress"
|
||||
isRequired
|
||||
disableAutoFill
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
alertClass="alert-danger" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<textBlock
|
||||
cmsWidgetName="QuoteEmailTextBlockWidget"
|
||||
typeStyle="caption" />
|
||||
</div>
|
||||
</div>
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
alertClass="alert-danger" />
|
||||
</div>
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -82,7 +87,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
|
|
@ -157,9 +162,10 @@ export default {
|
|||
|
||||
var vinByAddressPromise;
|
||||
if (zip) {
|
||||
vinByAddressPromise = baseMixin.methods.dispatchStoreAction(
|
||||
vinByAddressPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.IS_VIN_BY_ADDRESS_PERMISSIBLE,
|
||||
zip,
|
||||
{ zip: zip },
|
||||
"estimate",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
|
@ -238,13 +244,13 @@ export default {
|
|||
|
||||
if (!zipCodeData.isValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
|
||||
if (!zipCodeData.isServiceable) {
|
||||
this.displayNonServiceableZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayNonServiceableZipAlert = false;
|
||||
|
||||
|
|
@ -252,12 +258,18 @@ export default {
|
|||
const vehicleChangedDuringPolicyLookupInHeritage =
|
||||
payment.isInsurance && payment.insuranceCoverage.coverageStatus;
|
||||
if (vehicleChangedDuringPolicyLookupInHeritage) {
|
||||
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
||||
navigateToHeritageFunnel({
|
||||
shouldSaveSession: true,
|
||||
pageNameToLog: "estimate",
|
||||
loadingModal: this.$refs.loadingModal,
|
||||
});
|
||||
} else if (this.$store.getters.order.referralNumber?.length === 6) {
|
||||
await this.navigateForwardWithSingleCarMatch();
|
||||
} else if (this.isRepair) {
|
||||
const supportingItemsPromise = await this.dispatchStoreAction(
|
||||
storeActions.GET_SUPPORTING_ITEMS
|
||||
const supportingItemsPromise = await this.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SUPPORTING_ITEMS,
|
||||
null,
|
||||
"estimate"
|
||||
);
|
||||
|
||||
const promiseResultMap = [
|
||||
|
|
@ -276,7 +288,7 @@ export default {
|
|||
);
|
||||
|
||||
// call saveSession here - navigateWithSaving saves too late in the flow
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: "estimate" });
|
||||
return this.$router.navigateWithSaving(
|
||||
this.navigationScenarios.CLICKED_FORWARD_WITH_NO_QUESTIONS,
|
||||
this.$route
|
||||
|
|
@ -343,7 +355,7 @@ export default {
|
|||
funnelHeader,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
buttonQuestion,
|
||||
Form,
|
||||
alert,
|
||||
|
|
@ -358,6 +370,10 @@ export default {
|
|||
.vinlookupquestion p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.vinlookupquestion strong {
|
||||
font-weight: 500;
|
||||
color: $black;
|
||||
}
|
||||
.vinlookupquestion p:nth-child(2) {
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 1rem;
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
|
||||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
|
||||
wrapper.vm.navigateForward = jest.fn();
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -157,7 +157,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
});
|
||||
|
||||
// Mock store action call
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -201,7 +201,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
wrapper.vm.navigateForward = jest.fn();
|
||||
|
||||
// Mock store action call
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -242,7 +242,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {
|
||||
return "";
|
||||
});
|
||||
wrapper.vm.dispatchStoreAction = jest.fn();
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn();
|
||||
|
||||
await wrapper.vm.navigateForward();
|
||||
|
||||
|
|
@ -315,7 +315,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toHaveBeenCalled();
|
||||
expect(wrapper.vm.$refs.navbar.updateButtonText).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Button Text should revert to initial value when registrationZip textfield has new text", async () => {
|
||||
|
|
@ -330,7 +330,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toHaveBeenCalled();
|
||||
expect(wrapper.vm.$refs.navbar.updateButtonText).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Button Text should revert to initial value when serviceZip textfield has new text", async () => {
|
||||
|
|
@ -345,7 +345,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toHaveBeenCalled();
|
||||
expect(wrapper.vm.$refs.navbar.updateButtonText).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -379,7 +379,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
return { data: { vehicle: { carId: "C00000" } } };
|
||||
});
|
||||
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -417,7 +417,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.setData({ registrationZip: "00000" });
|
||||
navigateToHeritage.navigateToHeritageFunnel = jest.fn();
|
||||
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -449,7 +449,7 @@ describe("license-plate-lookup.vue", () => {
|
|||
await wrapper.setData({ registrationZipCode: "00000" });
|
||||
navigateToHeritage.navigateToHeritageFunnel = jest.fn();
|
||||
|
||||
wrapper.vm.dispatchStoreAction = jest.fn().mockImplementation(() =>
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
vehicle: {
|
||||
|
|
@ -703,8 +703,8 @@ function setupMocks({
|
|||
|
||||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => "");
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
|
||||
return { wrapper, apiPromise };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,88 +1,91 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<div class="row mt-2 mb-4">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="LicensePlateNumberQuestionWidget"
|
||||
v-model="licensePlate"
|
||||
isRequired
|
||||
customInputId="licensePlate"
|
||||
validationRules="license-plate-required" />
|
||||
</div>
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
<div class="row mt-0 mb-4">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="RegistrationZipQuestionWidget"
|
||||
v-model="registrationZipCode"
|
||||
customInputId="zip"
|
||||
mask="#####"
|
||||
validationRules="registration-zip-required|zip-format" />
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4 mt-5"
|
||||
cmsWidgetName="LicensePlateNumberQuestionWidget"
|
||||
v-model="licensePlate"
|
||||
isRequired
|
||||
customInputId="licensePlate"
|
||||
validationRules="license-plate-required" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="RegistrationZipQuestionWidget"
|
||||
v-model="registrationZipCode"
|
||||
customInputId="zip"
|
||||
mask="#####"
|
||||
validationRules="registration-zip-required|zip-format" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="email"
|
||||
customInputId="email"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
|
||||
<alert
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-3"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
class="my-3"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<div class="row mt-2" v-if="showServiceZipField">
|
||||
<div class="col mb-1">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
inputId="serviceZipCode"
|
||||
validationRules="zip-required|zip-format"
|
||||
mask="#####" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<alert
|
||||
v-if="displayVinNotFoundAlert"
|
||||
class="my-3"
|
||||
cmsWidgetName="AlertVinNotFoundWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
class="my-3"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
alertClass="alert-warning"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<navbar
|
||||
ref="navbar"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
<div class="row mt-0">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="email"
|
||||
customInputId="email"
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
</div>
|
||||
</div>
|
||||
<alert
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-3"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
class="my-3"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<div class="row mt-2" v-if="showServiceZipField">
|
||||
<div class="col mb-1">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
inputId="serviceZipCode"
|
||||
validationRules="zip-required|zip-format"
|
||||
mask="#####" />
|
||||
</div>
|
||||
</div>
|
||||
<alert
|
||||
v-if="displayVinNotFoundAlert"
|
||||
class="my-3"
|
||||
cmsWidgetName="AlertVinNotFoundWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
class="my-3"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
alertClass="alert-warning"
|
||||
v-bind:isDismissible="false" />
|
||||
<funnelFooter
|
||||
ref="funnelFooter"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -91,7 +94,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
|
@ -199,9 +202,10 @@ export default {
|
|||
async forwardButtonAction() {
|
||||
this.resetWarningsAndErrors();
|
||||
|
||||
const registrationZipValidationResponse = this.dispatchStoreAction(
|
||||
const registrationZipValidationResponse = this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{ zip: this.registrationZipCode }
|
||||
{ zip: this.registrationZipCode },
|
||||
"license-plate-lookup"
|
||||
);
|
||||
|
||||
// Settle promises and get results
|
||||
|
|
@ -214,9 +218,13 @@ export default {
|
|||
// If serviceZipCode is not set, then sets the response to the registrationZipValidationResponse. Calls VALIDATE_ZIP if the serviceZipCode is set.
|
||||
resultKey: "serviceZipValidationResponse",
|
||||
promise: this.serviceZipCode
|
||||
? this.dispatchStoreAction(storeActions.VALIDATE_ZIP, {
|
||||
zip: this.serviceZipCode,
|
||||
})
|
||||
? this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{
|
||||
zip: this.serviceZipCode,
|
||||
},
|
||||
"license-plate-lookup"
|
||||
)
|
||||
: registrationZipValidationResponse,
|
||||
},
|
||||
];
|
||||
|
|
@ -230,7 +238,7 @@ export default {
|
|||
).catch(() => {
|
||||
// No VIN found.
|
||||
this.displayVinNotFoundAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
});
|
||||
// If no VIN was found, stop processing after displaying alert
|
||||
if (!vinLookup) {
|
||||
|
|
@ -241,7 +249,7 @@ export default {
|
|||
const isZipValid = resultMap.serviceZipValidationResponse.isValid;
|
||||
if (this.serviceZipCode && !isZipValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
|
||||
|
|
@ -260,14 +268,15 @@ export default {
|
|||
this.displayMatchedDifferentVehicleAlert = true;
|
||||
|
||||
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
||||
vinLookup.data.vehicle.carId
|
||||
vinLookup.data.vehicle.carId,
|
||||
"license-plate-lookup"
|
||||
);
|
||||
|
||||
// Update button "Continue with..."
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
`Continue with ${vinLookup.data.vehicle.year} ${vinLookup.data.vehicle.make} ${vinLookup.data.vehicle.model}`
|
||||
);
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If the either the registration zip code or service zip code are not serviceable
|
||||
|
|
@ -275,7 +284,7 @@ export default {
|
|||
if (!this.isZipServiceable) {
|
||||
this.displayNonServiceableZipAlert = true;
|
||||
this.showServiceZipField = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// If the registration zip code is serviceable and nothing was entered for the service zip code
|
||||
|
|
@ -312,9 +321,10 @@ export default {
|
|||
return await this.navigateForward();
|
||||
},
|
||||
lookupVin(plate, state) {
|
||||
return this.dispatchStoreAction(
|
||||
return this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VIN_BY_PLATE,
|
||||
{ licensePlate: plate, licenseState: state },
|
||||
"license-plate-lookup",
|
||||
false
|
||||
);
|
||||
},
|
||||
|
|
@ -367,26 +377,26 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
licensePlate() {
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
},
|
||||
registrationZipCode() {
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
},
|
||||
serviceZipCode() {
|
||||
// If they modify the service zip code, then hide the error message.
|
||||
this.displayNonServiceableZipAlert = false;
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
textboxQuestion,
|
||||
|
|
|
|||
|
|
@ -248,6 +248,13 @@ describe("partQuestions.vue...", () => {
|
|||
},
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: {
|
||||
glassPieceParts: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
|
@ -281,6 +288,13 @@ describe("partQuestions.vue...", () => {
|
|||
},
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: {
|
||||
glassPieceParts: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
|
||||
|
||||
// Act
|
||||
|
|
@ -328,7 +342,14 @@ describe("partQuestions.vue...", () => {
|
|||
},
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreAction");
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: {
|
||||
glassPieceParts: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
const spy = jest.spyOn(wrapper.vm, "dispatchStoreActionWithLogging");
|
||||
|
||||
// Act
|
||||
wrapper.vm.forwardButtonAction();
|
||||
|
|
@ -336,7 +357,7 @@ describe("partQuestions.vue...", () => {
|
|||
await nextTick();
|
||||
|
||||
//Assert
|
||||
expect(spy).toHaveBeenNthCalledWith(2, "getParts");
|
||||
expect(spy).toHaveBeenNthCalledWith(1, "getParts", null, "part-questions");
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
|
@ -362,6 +383,13 @@ describe("partQuestions.vue...", () => {
|
|||
},
|
||||
};
|
||||
});
|
||||
wrapper.vm.dispatchStoreActionWithLogging = jest.fn(() => {
|
||||
return {
|
||||
data: {
|
||||
glassPieceParts: [],
|
||||
},
|
||||
};
|
||||
});
|
||||
wrapper.vm.navigateForward = jest.fn();
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -145,7 +145,11 @@ export default {
|
|||
);
|
||||
|
||||
// call API parts method
|
||||
const partsLookup = await this.dispatchStoreAction(this.storeActions.GET_PARTS);
|
||||
const partsLookup = await this.dispatchStoreActionWithLogging(
|
||||
this.storeActions.GET_PARTS,
|
||||
null,
|
||||
"part-questions"
|
||||
);
|
||||
|
||||
const glassPartsForStore = partsLookup.data.glassPieceParts;
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,70 @@
|
|||
test.todo("some test to be written in the future");
|
||||
// Components
|
||||
import customerDetails from "@/layouts/payment-method/payment-method.vue";
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({
|
||||
submitWorkOrder: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("payment-method.vue", () => {
|
||||
describe("navigation", () => {
|
||||
test("if the back button is clicked, navigate back", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks();
|
||||
|
||||
// Act
|
||||
await wrapper.vm.backButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("if the continue button is clicked, navigate forward", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks();
|
||||
|
||||
// Act
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks() {
|
||||
store.getters = {
|
||||
damage: {},
|
||||
lineItems: [],
|
||||
order: {
|
||||
payment: {
|
||||
piaErrorCode: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallowMount(
|
||||
customerDetails,
|
||||
getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
navigateWithSaving: jest.fn(),
|
||||
navigateWithoutSaving: jest.fn(),
|
||||
},
|
||||
store: {
|
||||
getters: store.getters,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,87 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" leftAlignHeader />
|
||||
<hr class="mt-0" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<loadingModal notFullScreen ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<cart
|
||||
:damage="damageInfo"
|
||||
:orderLineItems="orderLineItems"
|
||||
:availableLineItems="cartItems"
|
||||
servicePackageOptionsCmsName="ServicePackageTitle" />
|
||||
|
||||
<div>
|
||||
<alert
|
||||
ref="paypalErrorAlert"
|
||||
class="my-4"
|
||||
v-if="displayPaypalAlert"
|
||||
cmsWidgetName="PaypalErrorAlertWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
</div>
|
||||
|
||||
<add-vaps-modal-buttons
|
||||
:cart="cartItems"
|
||||
:wiperItems="availableWipers"
|
||||
:rainDefenseItem="availableRainDefense"
|
||||
vapsTilesCmsName="VapsProductTiles" />
|
||||
|
||||
<div>
|
||||
<button v-on:click="ccdPayment">
|
||||
<img
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAcCAYAAAAjmez3AAAAAXNSR0IArs4c6QAABc5JREFUWAndV2lsVFUU/t42e6cznWmB0mnpwiKgpagsNRIQikECUWM0ERSXHyyaCOGX/BCixkiMBhOwStBEraZG4g8DyCagokLLolithXaYtsPQ6cLMtNPpbO+N575hGohpmZElradJ33v3nnvu+c75zrl3OKTk8V1F0OsKoJU1qaER+5QSPLqjHdAZ2vHFyl7mJ4ePTkl8u2fNotLcTcVWwxiNwMdGLICrjvH07ArFNCc8gcPO857VqF7hBF76alXVp/WJUExJjDY52h5MTH//8K94cVeOmF9geaGq2IqfnF2IyMpIT8agfxzHwSDxsBkNcyReXyjGFKXk3Z9b4PWFiGjcoOJoebFlaSOmcWaHSMgUkScAIjFvFAKhgJPzisLq5n8hIkNBZc7+qdhGGyrmOhOR0aoiPxt+qxGMYcMJYx5TuboWKSNsTWqOtQumk9JL6TDbPCkl6O9fPYXmmH5KUmtS30M92Z7d/dGEqzuSELN1IqqX3wNHtn4o/cHxmJyAJFy75eDUDV+Yc75wFCaNCDqrbqifrsLGfQ1478JlqNRKJwIKUa/O7cOOEy6KQgSTck3YtnQ69QgOjV1BbNjboDq4bek0HHX2oPZsG5ZOy8f6yhJ8WNeK2nNu9AzEkCUJmF9qx7rZE1BgTgbviLMbW4+dV1M9dVw23lg0WQWcDpgUO9IODU8OzyQKzhhnxqEGD/b87SVqJRAngFuONOHAb27kmrTI1kmornPhcKMXuQaNqvfyt+dwwu2Hhmw0U5uvOevGpUBY9TMQjmHrj8042NSJg40d2NvkRV9ETgfDdTpqRq4bGeZDT9GcV2wDtCIG4jLRjMduArXvzw7kUIY2PlACH0X9d08A1hwj5jis+ICyIUdiWFSWj/3PzUHLlX64CMxsmmOy73wSwFMzHfjmrw5E4grCZDtTSTsjKcMmjQCjXqLNFDjJqXeOtyBI3F8zawKm5mWh9o9LiEdlPDwxFw6LHhNtBqpyHsdauvDM12dg1kpYWJqrmvMNRLH5+yYYKIur7y+iORF+ylAodgeAGKlYbXqNSqnPiCJ1riuYnG/BBqoFmWj2XVOX2soXUh2wol5V4cAr88rUWqqpb8Wyz0+iPTCgAtlO9Xbhci+WTBmDBSV2GCnjQcpeOJb5VSnzjNBmNoOEUDiO7Sdd4MnZN6k47UYNTnv8VPh9sFr1uJfAMWF0ZE1h98pZKB1jxsmLV/DluUtoIzC7TrWxnoz+aBxv/9CM/lgcCoHoJTCZSkY1wowbKf0WogI7DHqI608Qtx+ZlKfue6i5G77eMCrL7JhkN+LVg41wU1E/O3M84tS61Usphc5OgWAF30agdQYtdUM/fmnzEaAkpTqpK2YqGQPR053MSjVCnsFEZ8/6ymIYiG5RAna8tUcdryy0qjXE2mrdxR7UUBdThdY+WV6AKfYsAlkPjrJVvexuzHJYEKWaW03drY5qqaPvvwLh0mcYuz6/9tBdWFFRhDyi09zCJIVANjbNn4J1c8tQMdZM9NNg99OzcYbo1uYfUFt1kcWIKmoCrOXufKyCruECqkptg6f6juUz4KGMTs0zZZCQ5AEtcohD6G+CAj5550rDRLlJQLmZDNA5IgfciNNTIIAP2ikg7N4gexH3K3BQ/TgK6buAji12cvEhJEJeUB/Do4WCOiYHLqsg2bb3mWnMwtZ3quvZ2HAiaHgIMWKBVgtRo/TB6KpBkH4C007DrbuJOXJOldQ5fBOmrllqEGIwBhdz0M3gkjXCUSTU19sF5Jrdb+UrxwKTDNIo83zoKFztWozDmR9CQ5u9QzOqz0m6igonxWWdA5JkpN1HV4JESUaUN/OIc1GxM6g9sEdYu3ZVRT4BubXFeLvzwi6Xp8/Wh3CxuVnsaO1+a/P+hnKOEyoXT7Cod6KRDoe1eifdKuiuphxp6Xod1gF3suSf/8SRM3n85rF6aUlCSWjoLBjpWPionGht8fZ9DEP2TmxZEP8H777k8QqWrnUAAAAASUVORK5CYII="
|
||||
alt="We accept Visa" />
|
||||
</button>
|
||||
<button v-on:click="ccdPayment">
|
||||
<img
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAdCAYAAADoxT9SAAAAAXNSR0IArs4c6QAACeVJREFUWAnNWOlvXFcV/923zMyb8Sz2zNhO7MQ4aUziJM5aO6UkzcIiRKpAIUgEgYQoCRKrhADxL/AFqSCkqiDxgUoRpCBV0NIGsOmXkIY0G06Mkjh2HDteYs+MPZ7lrfzuc8ZxkxnblfjQO3rL3HvPPed3tnvuE3jYenq+GysWi0nADVT6PqxPEVI9x9OLqUhhqq/vtyUpp5C31tbjRiIRO6xp4jnLc9Ps9GT/h7UJRXgKxJBr441r15SrwMu2BKJu2/uNE6qjvAQhEvBWj0ESC873hIROOj5XS+3TcvbC/NXTLSrX5+n0w1W/pWk4Lzr2nEwFXeefqqJ1eq6zOK/WixRAo9BBuDCokjgsRDwHJaEgiwAKiooyVFhVQEnaoOr6VzRgIRGwoSsecqbGS0fJVlByVDgSnbwkwXKNPF3XeSccDD+vRVw94sHdshIIuS55otErocuZxQ4niw1ugaK7FNuDQ642r2ERxhUthstqAuMiBMfXnEBQc/BUtICdqTx2NeawJmJCE540IhxXIG8puDFTh39NxHEjE0GGwDi8PBjPleP7FKWgia7uF9sVWwx6y7qUgOHZ2G1n8Wl7Ap3unK8syUc2isNf5d8C71sE9Fe9Cee0JGJhF0daH+CzbQ+QDptLvLei8ke0pqPg3EQCbw4ncXU6CterzFngVe0eDkWTWrWBpX0uRYzShb5kjeBT9hSoYzqTsnSK/y7BLG0dXgFtpSF8JF1E+3YLzzRn6TKKr/2l8xbeH9FKV/tEyzR2JOfwmxst6L1XD5t0K7UVgYTo/5+zxnCUlpCtGohqTCxpI0XgcGwaSZPgSxRmFYld2qbsKqgPWTjVeQ8W3a53tGFFN1sWqvT7Z226hD3OSBB+HFQTulqfqSpQ6gXCOQeFAcCeoojLcnv/KjJuokEbX98yhvZokfG3PHHNUQmi2S3hBWuc7uQuiYD3M6z2T2G8ZWJB6F2AvpOG4FPQGp65dLbUvbxqNwmmNVzC5zdMIqzZ8Pi/VqsJRFpgDzNTKzOTXeFHAZdPCgtsZKZKlspwr9AV3+N1ie5ywYN9288ynMQFZRD7gVxZvLqI0rWeWzuNj8bzzIDV58jeqjFCdkhQfTuYpfS1ayCiUZg3b0Otr4fW3AhzcAheiZWBzJ2PNUEXKEVVhNpc6NwzFpOhlNeg7NSKYhjQUhsgNB32/ZtwrTwHq+mUG4OqIZZsxtOtFq5NS8me5ClFqEbtx0MDgWwuzaDuq19G+qc/ghIxkDhxHOmf/BBaIgERNqDEoqwLVIhgEKrso4ChZ7oR7doGpUjJRZRsI/AKZGPpwGwIMEOIdB9H/OiPET3ybaiNbXS7KJRQhPNZeOhBKAGDryoULQitYR0ih0+hZyN5MFJqtaoWkZOjzFZxz0Kwazu0da0IbulE3dHPwMsXoK5tQvxrJ6DFY8ic/gOMbZ0w9j2N/NlexL5wDF6xiMzvAojs6/Gtlnv199CeaoexaxetMg5jzzHMvvFz2LkxKmUtYruOcVe0MX/hDIIbu+knQZTvXESofS89YjP1kUYqMM+KgJWDNEqVVhWINF7MKVHLcSh0K2dyEvGvHIdqhFG4MQCWnvQRB0b3XrimifDBj6N8bQDW5BS0xjQKve8g9Z1TMEfHYNA61sgIQgSrb9wAz5qBPT6IUv/fIeg2WiTJpWyENh+AW5olkB7YD4ZppTACbV1UXAZuIQNRzCEWjGG+4OvmCShVXUvOcmz6eHMzPLqOPT4JY2snNZiFPTKG0NbNUBtScB2Hwo7CujmIQEszGa+HM5PF7Jtvc5y5X1GYdh8wBkzotOr0S7+CM52DV85xjNEUjkNfv9OPFc8qMfZV1k4e5vpegZ5qQ+Hi6yjfvQQnM0aQc4y3muJWjxEZlzkSKQxsj8K7hSJKl68BJZMWKCP+xRdg3rkDN8vdenoGmVdPQzBG6g4dpFD07boIQdoovnsRudN/hDnwXyh0Q2d8Ag61rjVtQmDzfuhtexDedZRgbzM1EwjBoEyBbZMWYUKIN9G9umFn7sIsm8hZWrX84ltHbWrZXS9c8X3/38ObTL0BLvbsugRD1UZ56C59dohnLgfFcxegppPUeAIOgSgM9MjHevieQ763D4H16+Dm88xG9xE5coBBHEB5cBB6OoV8Xy/U5DjjbS9CHQdg3f8P1LoGqKEY3LlJWGPX4doWzNvnmUDqYHQegtw6rOv/wMBQFm+N0kOqZC1dC/6satEo40kWiSfVSXxSZFBmiSGYaoXBSiuThVaf8LOVZ7IQCejMXtT2DH1/JoNgcxPy7NeaCoh1NLMvy+AvLVipkIPW6jH26iloBE5ukqk4Rm70ASrJIwhpUbcwy3UjzIzMirSUYhfx8tU0ztxslontiVazaJRz57gVv1fSsZ+uIO0p3c2bY9XLmJFB7Te5j8iNgkENxoMcM++NUosswZMC5vBt0qr+mJMrEgT7gzxD5Ge41rSfYt25h2tJTcvlylyZa3nmvB8XKmNpqhzApSkqi/w0X5IF9kvvVbOWnCDPGJf1egw4CWx1Z/06SwavbIJHsppN11nSUKBbHspxHZoupSNNkDRrJTEv7hHy4TcJdGmrDFD1iqzXeCh5aySNO7NhX6alU5e+15RIAsnSKq8F1qC5XEI99xRpldU3gVw0gOZ2ugvxCxaQSpBS1tgHHl9X4hEs6a9PR/CX4TRMJh9d1Cau4nGPltTJVZ70zugtKFBzNPijwRXfPERHTcz9my6WV6CFK6pekXAhM9ESI6wEfj3QioliYFkQcsWaFqmwk1nirNbIiQ6etybQAKbG1eChP896Ov4mUmhRbew3szBYe0k4tcgrULk94U4+glf6W3DlQZ1/JK7IU+vpA6m1sCSSVnAp1J/1NbilRHlKnMROVsUGraXxzLwg2MJRV65j049MUvWrUR51G3EZCRgDDq5kozjWPoXWaAkBhbS+NiqikwcrYZMHqll+iOgbrcfbd1MYng/REstJR4YPh7WgF7DLjllmHAdrkVTO4/1qDMOKgQ43STA5bHTnmaYdP5PIDw9lut+QMPjxIY7rBD3H7CVjrWyrOHsvxeo1hu08wu5Jz6IlUkZAWohYLJ7TZ/nxoZ8fH87z48PQnOGDWgmEVAOTNtMq0/XOgz9IONm5P/Fz0AEWPfJDybJNjkt3k09pkRg3zDruOUVaIsezrCUjm03OqOjb7+CN1Yc/Iu8GD0pxngArn4PytETlQ4OgFR6nlTRLmxzngcB0Pfe1ZCJwUv4Xu3e/eMj2xC/5Zw33BZWb30p4FteUE6Xryf3ksUS6OKfai0/Hm3yqJJeCfJBGqDzZOO8qnvO9TZvmBxfpd+z4ZocrrL2MCibK1QP5IMz/X3OpaeJ3R4HIuUuXfuHvqP8D/8z6CVVrJhwAAAAASUVORK5CYII="
|
||||
alt="Mastercard" />
|
||||
</button>
|
||||
<button v-on:click="ccdPayment">
|
||||
<img
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAdCAYAAADoxT9SAAAAAXNSR0IArs4c6QAACTtJREFUWAnVV1lsVNcZ/u69s49nt8f7bmPAxoQlFHBQCC2QVlXaSDRV+lBVClIkIrVqVSlCygNqH9qnqkIVIqryVClto0ppHvrQJCookAAhIYGAw2IbG+/7jGf1LHf6/WfwNoUi98k9xshz7jn3377v+//RIOsH7xioHfVAt3lg6oba26j/5XIabHoOKS2O58oX8NJLeXFVA07pOFERhtPci5zRjULBDegbNQzQPw2aloCW60UMH6NhdgKnTpkaTv4hhKT+BmP6Mb0P8uTGDWLZM+YfhQRM8yx062mMBUYtiBUOwtCO8UGQ0S4f3dh/0E9Nd8PQj6OQuYSqiYiFH9pRMH0b2/FHeFcwuan7yOkm5OHTiSQrK7GxCf6IOJa3NNMB3bRY1Ib2n8Swajp0cl7QZvKxIbrAf3luyK+FgiDPl28KbJeWIHTVZ5PnNeHoqvfpqz5rPCt2CvzRH9qRO+AZsSF/5+SpFKF0aZq6WQyk5KGD8e2sL0fIaadDGpKZLOxWA1ZDRzSVwZ2pCOr9boTL3Mo59S4aEf+XIijGUaATQFzuWwzYeF8+R1KLcNgscBrGcjLy3E9lc3DRjkHvU9m8CszOM5l8HjfG5jCVSJHfRSslLjOxpYsHd9eH8PrhHVjMmqj0OtEY8GA0ksRwJAY/g/t8ZBrdVUEkaLjW54KNTj6IxJHO5OGmg83lXoxHE5hPZlRwTpuBzsoAxmNJNAa9mI6nMDKfgM5oN4V9TEwUDgawRc5EEnxXDA20Wcff/ukobFYdv3n/Gt7/ehjpQk4lt9TttYEwWAvf/vyWRnw2NIP3bg4g7HHi19/Zgz9fu4e/8PfF7mb89Nnt+MetIZw5fx09m2rhtdtwbXgKTUEfmkIezDPjf7p6FwGnTRVoaC6B73Y18Mw0jm5pwJXBSSQXs2hiwJeHJvGvuyOwWDT8/sUD+OuXfXj7ym0c2lyHV5/pwtmPe/FMSzXyTLBA8HFLUL6y8iZaK3yoJWx6J+dwaFMdsrkCPqOTMRre1RDGvdkYvhqdwc3xGbRVheBhEAJnqUqFx0G4OVDGPbthQVu5X2XZSagaJIihGfiC1RSI/Oy57TjR04WBmSgOttcQZlZ8MjCBdC6Pp+l430wMF/onEE9nVtNtxdeSv9ZUxGA1epqrMcbyDs4u0MEC5lJpZv+BInhPUxXusdRnLtxEQ9CjIJhlEInFDJ0sckTxkXsmNbF3cpYV1skvHRVuBzmm8ZwGCwPKkhSfDk2hb3pB8WEqnlSVMU0T3bUVsNQBH/WPE44J5cd/KYYKaaUiLF044EJPW5XC+zc7anGkox7H921RRseI+aDbjm93NiBD8h5g1ppCXsUJt90KK7MeY0ARisFiPodKjws/P/gUfkVYthBuI7xP3cI+JqOScD19/gZOf3QDu1nl57fU47UD2wgdDYNMooucOvZUK4KEpgSgS5aesNZUpDXkRzWV6OjmepQ5rfigdxg76ivwkz0dmEumsZVkdBM2vzi8EzU+N7IMvomVybEMbQxqgfBzWi0Iuuz4XlejMj3KAI5sbUAZA+2uCaK1vMij25NR3surZJy7M4ptfPbDnW2YI+zq/GWEWA4v79qESDqtEpgh7JUkPyaglUCYjTEqz5mLX8FLZZpPpHGpbwyNJGR7VQAODsUXBsYRS2Xhd9lU9l02VoKSKoxWOSMsCuTCFRJYspti5VJUPi8rJhAr6OwHjHqEdoIuJ8XAjn/eHsGn9yforBNbqwNwUvVytDMdW4TbYcBjsyFBNRTJl64t9nJMING7Zq0KBCTeAgam5otgpyMUdESo39epNkurOuBFd10IDpI7tZhHilyQwX86nsYQ7zZV+BEgHyQ0MSaYvz+/oJy2khvscfA67Jhm5hcp33Y63tNaw5MFyncOk9Ek7HYLIcsWXNAxNB+DhdDaUVfOXhTG8HwcN8bnEGf1V6+VQGRXhF1fu6VSvbTHTOxtCeOVvZ1KTRLZ4sskSx67ndzIwmWxKoVLkydKzViJer8HHqcFfewXcd6RqUDEwuOwquTFMxlWzFCwM9m+R8mTBB1tLvcoBRxmz4lSdMIeN8VmHqMLSdqnbQWDYjglXq+O8dF/lxFOlwbH8eGdEYRcDsLQRhikUc3GKNx6t3cA9ynRo1SbjvIAFtKECDP8NEl9jnd8vBOjpHocNmyvC7LJjaLcY0eVt4xZzuADQk16jIdwPEjBuUtVE7n383wneeQm1ERdS9eKapU+ecxngYtkWpyTnrObYvDG0R0Kaiffu6SI/sr+zdjOHvP97kYc7qgjYbNKzUJUsmfZM7bVlFPlNPKlABff0044dpGHIaqiQK/C68LmmhAr5EeO40mAwW+i0Mi9zuqgknJFzVU+rjuQpbsUEWUkS0MZ9gShlN2i00HygN7IOPL25324MTGLl3e2o6PSr3qSdPc7MxFFdi9FQ4gkPUX4UuUpw6v7O1VgV9n937r8NQ6112J/cyX65xbwu3PXSXQT9RxzZAYrkrno0bqhJYYNks/BlOiiTBzu3rp8WwXxy2/txMX741SYec5JZXiNI4Y0SxfhcG86gj2E1wudLRxHgDcv9hKSKQUTg2omjXSWEj/JPZNZEugFKRoyd8UXc/BSFGRq8FHpGH5xCTweomzdgcRIzN11YeLfr2Yq0fYjHR7CwqEa4bHtrWq/gnAQZTn57ieoI2mP7+vEC1sbMTgXRTsHxR/tbmPwFuyqD6tpWCZemYZlPiuQ8LUBN7oIrwiDe0ClqiLcWmhzipXuZaIWiQQ1ej+MaX2BMPqrg9OEwi342BiXsiHfIQYpk3ep9Z2ciqVzaySkZHyQTkwnF3HW5B2q1Bjl1cdsy5Rs8h7RqHqOJPccIcZ41HeSCEXinS/6OW3b2DsII9r+25f96J3gOM/pmUeX7Uss6wyETXMhgbGbCbm7dj0s8fl7oyv7skf4RdkYx3ofFPdlT5z4X9fDd5Zet9AQ2+Q63iyslpc9bj3qWemdR5153PueuC96xaaMfJ5zsqaG1ife2XgH5PtvmiNAll8SjNsENIcjSsf/05IqF/IzdLkfzlSUX5xxAcnc3zkwHSPTQhwxpVYbd7Fl8UemxiQ0448cwa/hfnOyiNbX3/RxLvgGq9IFzXQpUd+4oZCjRhqmdgtluIzfnojQ1cK/AZjByIeFlRG5AAAAAElFTkSuQmCC"
|
||||
alt="American Express" />
|
||||
</button>
|
||||
<button v-on:click="ccdPayment">
|
||||
<img
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAcCAYAAADMW4fJAAAAAXNSR0IArs4c6QAABzFJREFUWAntWHtQnNUV/30P9gXLJkvCsjxMDJKFQEhIY4MghPicSZppp0k1YrSjfzRxJmrHmT6i7YxjHyoznalmDK2vPMyIxqnptDVV6yNgiAQTUggYwIUQhbDRhcAuj+V73Z77bR6Lgg9KLJnpnfn2u/d+955zfvf8zr3nroBzhT0MMdiKFIsTaVIiZmnnP8zQt8xgqMMIjobQk1qFIDdT4D/sXljDnszVlozvPs7c87KEOLvC+2dyEUBoImEBwc46pePtn7keDR0WyCOOCLKWI/fmvbY1D3tgT5rJGMbbxgywPr8e+cevG/HB3h+Io7jKbSSlrZIyi+deVkA4LEGE4EyV5MzSK3TvgqtF1ZFgh8UxT0r0iuNhXyYtUYLo8lphdaVdngAmWef/g5lkYf7n3ZfMM4yxGHAMTBkFGxmgcyC2P2bIN6iOl31xonyxerHGBweDQfxt3z6MRMaQnpaGgmUFSKV3U2MT+oKfobCoCNXvvovm5hbk5y/GdTfcAFmWUV9/GIfeq4Wiqli7di3yc33QTrwBrenvMAZ7IWdfD9X3PRxsPgmbzYayVWXQdR2dH/lxpOEochYtQs2BA9A1HUlz5yA3NxfHjx9HaHAQWQsXoqBgGYZHhnHl/PkQJemi0VSbFEzg9Gk89+zzKPhOAVqamrBzxw78ZNMmnOzqwpHDdQicOYOdzz2PdT9aj97eAAb6z6K2thaVldtRUlKKxEQnmhvqkRl4A0JdJamic1iQoFb/G0rLv/CxWorXqutMY61WC/a+shc93T3QVA0v7NqNDeXlsFltaD9xArt37UIxLd7+1/aj8JpCbLnvPgiCed5/NRg+QhBFJKd4cMedd5orsu2PT2DPnj3khXzY7Q4MDgxC0TRkZ+dgBSnoC/ah6sUXsXnzZtyyYYNJp0jwFLTd5STMAESrqVggQNaQHytss/CW3Y5jxxqQnp6BY0cbcO9P78fZgQF4vV4Ul1yLJLcb7W3tmDtnDtbfeitC4SF0dnZiDrUnKhN6JnagYRhwOp1Eq2tQe/AgwqEQRKLTD9evQ5hcX/HY41iUtwir16xBJDJKNMm9MN3CVOgCZXmxq0gLKlIqkpYAFBUVY99fXkX+knzEk468xYtRfaAa7R+143eP/IZk5WDp0qVE+T78/pHfwmq34vaNGy/I/3zlKzcAkQwZGhpC3ft18KZ6kehygRFAF71/+auH8GjFY2hvbcWhQ+/DYbOjra01qoPm6ZKVxhKvY4Oe4p/Rye1MX0iUKUJjYyNerqpCWVkZ4uPjYeiaCWrb9qew9cEH4U5KgiVOxiz3bGKEHUXFxZ/HcKE9sWdIuU4G957uwQ6KC53aZ/v7sOmee9DW2kb1fvypshJtxGdPSgoFsIGVRIucbB+eenIbGogyHGy6x43v56wF6v8MqBGAApYRNYXkPMSt3IIsVYLPtxCBQAA33nSTCZpTt4M2gyqidMa8eaahfKMoL78NTz/9DP5QUYFfEEgOzCym16PxI229MSVJtDlL5AXFBYJ7/oUBcZKMxFkueDwe09U/vvtuosMSWCkofTnZWEUrySmoKCo23rER15aWUn8OfARogHjPvxWVlOGKFauBpCySSwolF+Rl5bBc9wAkd7q5+6VnZJieWH71cjNOueHu2W5TT3xCAnJIZpbPhxWFhcjNyyOKCsigOQ6HI2qrpsDoblAUf807wmDF0izZlbrVdv3P7xIzV0YHTPsvnTNjI+YjOueauKZDBRsdgNb6OvSa7WGl/+RDE9NsOjSNkyFAsMabz7juqTa0MWid70H7YA/0jgPkLSpS3MTnzFR1XPJ5BELvqIHa8BK9D1IcDhEIS9TTtLF8S575b2ASRelurH9yBFr9TvONyCDt73FRIDGiZy4YfosMn4Hmp9Tmw39C7yRP0Lllrj9t+ROVmQeGqGSc+RDKsVfAuupgBP2gvy4Ig43o9OXmfvnXieBfij4OoJ9Sn65D0Fv2w+g9TqncMGnSCQDFhDw+oZzMhCgYFptvTDZ0mvt1FcbpJqj+GrCeo9ADJ4BQgOKA9ibuAboOU+PrK2VMkK2Uf+vQR5gaoWwwust9fQnfYKShgUXC9G9KB9S2t2F8fBjs7CcUFwRAIxrFcQ/QM4VCx7Eh0h9PsjXUPzgsxh1hgRYD2TebW/YU5H1xCtPB6P5ihOj5zA/9FBnffZTuND3EHqIPfQdl5qYXLFNnO09kBFkekwzWLMPfMyAuiX9TaX3rr4LDtV5cUAbYE0nZF+2btIfvPGNDtNKniPtdMPq6qM6fbgLTDYyGz9GHtlPOaA5iGkjAgVhkYTiisJeC0Bt4m9stfHp/cnKC17MFzuR1dOeYHZvo8jGTFuIqSRCYMSYRIAHKqGAoETKZp/4yiaLkkiOg21RUGY09V+WQmMFzaC6BJ2/nV/D8+7xWcyZoIi0b55TARMqFBVHqlg3jWWu6Uinc1RX5D8Lhz9D2zGBFAAAAAElFTkSuQmCC"
|
||||
alt="and Discover" />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button v-on:click="paypal">
|
||||
<img
|
||||
src="https://www.paypalobjects.com/webstatic/en_US/i/buttons/PP_logo_h_100x26.png"
|
||||
alt="PayPal" />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button v-on:click="afterpay">
|
||||
<img
|
||||
src="https://business.afterpay.com/rs/539-RJA-633/images/AP_logo_badge_6328x2204_blackmint_png.png"
|
||||
width="100"
|
||||
height="26"
|
||||
alt="Afterpay" />
|
||||
</button>
|
||||
</div>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -20,54 +90,174 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import cart from "@/fmg-components/cart/cart";
|
||||
import addVapsModalButtons from "@/fmg-components/add-vaps-modal-buttons/add-vaps-modal-buttons";
|
||||
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
|
||||
// Validation
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import store from "@/store";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { Form } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "payment-method",
|
||||
name: "paymentMethod",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
// TODO: Data fetching Promise declarations
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_WIPERS,
|
||||
null,
|
||||
"payment-method"
|
||||
);
|
||||
const rainDefensePromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_RAIN_DEFENSE,
|
||||
null,
|
||||
"payment-method"
|
||||
);
|
||||
const supportingItemsPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SUPPORTING_ITEMS,
|
||||
null,
|
||||
"payment-method"
|
||||
);
|
||||
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "wipers",
|
||||
promise: wipersPromise,
|
||||
},
|
||||
{
|
||||
resultKey: "rainDefense",
|
||||
promise: rainDefensePromise,
|
||||
},
|
||||
{
|
||||
resultKey: "supportingItems",
|
||||
promise: supportingItemsPromise,
|
||||
},
|
||||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
const clonedGlassParts = store.getters.order.lineItems.glassParts
|
||||
? JSON.parse(JSON.stringify(store.getters.order.lineItems.glassParts))
|
||||
: [];
|
||||
const availableLineItems = [
|
||||
resultMap.rainDefense,
|
||||
...resultMap.supportingItems,
|
||||
...resultMap.wipers,
|
||||
...clonedGlassParts,
|
||||
];
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
availableLineItems: availableLineItems,
|
||||
},
|
||||
"payment-method",
|
||||
false
|
||||
);
|
||||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.pricedGlassParts = clonedGlassParts;
|
||||
vm.supportingItems = resultMap.supportingItems;
|
||||
vm.availableLineItems = pricingResults;
|
||||
vm.availableWipers = resultMap.wipers;
|
||||
vm.availableRainDefense = resultMap.rainDefense;
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
return {
|
||||
cartItems: [],
|
||||
availableWipers: null,
|
||||
availableRainDefense: null,
|
||||
availableLineItems: null,
|
||||
supportingItems: null,
|
||||
pricedGlassParts: null,
|
||||
displayPaypalAlert: this.getPaypalAlert(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// TODO
|
||||
return true;
|
||||
},
|
||||
backButtonAction() {},
|
||||
forwardButtonAction() {},
|
||||
getPaypalAlert() {
|
||||
return store.getters.order.payment.piaErrorCode ? true : false;
|
||||
},
|
||||
vapsItemsSelectedAction(vapsItemsSelected) {
|
||||
this.cartItems = vapsItemsSelected;
|
||||
},
|
||||
backButtonAction() {
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_WORK_ORDER_FLAG, true, false);
|
||||
await submitWorkOrder({ pageNameToLog: "payment-method" });
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_FORWARD,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
async ccdPayment() {
|
||||
//leaving site, clear any save session promises that we will not be able to resolve when we return
|
||||
this.$refs.loadingModal.showModal();
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||
await this.dispatchStoreAction(storeActions.SAVE_PAY_NOW_TYPE, "cc", false);
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_PAY_NOW,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
async paypal() {
|
||||
//leaving site, clear any save session promises that we will not be able to resolve when we return
|
||||
this.$refs.loadingModal.showModal();
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||
await this.dispatchStoreAction(storeActions.SAVE_PAY_NOW_TYPE, "pp", false);
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_PAY_NOW,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
async afterpay() {
|
||||
this.$refs.loadingModal.showModal();
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||
await this.dispatchStoreAction(storeActions.SAVE_PAY_NOW_TYPE, "ap", false);
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_PAY_NOW,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
damageInfo() {
|
||||
return this.$store.getters.damage;
|
||||
},
|
||||
orderLineItems() {
|
||||
return this.$store.getters.lineItems;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
cart,
|
||||
alert,
|
||||
addVapsModalButtons,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
|
|
|
|||
66
src/layouts/payment-pia-return/payment-pia-return.vue
Normal file
66
src/layouts/payment-pia-return/payment-pia-return.vue
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<div class="confirm" ref="confirm" @click="forwardButtonAction"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
export default {
|
||||
name: "payment-pia-return",
|
||||
async mounted() {
|
||||
const piaError = getQuerystringParameter(queryStrings.ERROR);
|
||||
const token = getQuerystringParameter(queryStrings.TOKEN);
|
||||
const payerId = getQuerystringParameter(queryStrings.PAYERID);
|
||||
|
||||
if (piaError) {
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PAYPAL_ERROR_CODE,
|
||||
piaError,
|
||||
false
|
||||
);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_ERROR, this.$route);
|
||||
} else {
|
||||
if (token && payerId) {
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PAYPAL_ERROR_CODE,
|
||||
null,
|
||||
false
|
||||
);
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_WORK_ORDER_FLAG,
|
||||
true,
|
||||
false
|
||||
);
|
||||
await submitWorkOrder({ pageNameToLog: "payment-pia-return" });
|
||||
this.$refs.confirm.click();
|
||||
} else {
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PAYPAL_ERROR_CODE,
|
||||
piaError,
|
||||
false
|
||||
);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_ERROR, this.$route);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
return true;
|
||||
},
|
||||
forwardButtonAction() {
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_SUCCESS, this.$route);
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.confirm {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
347
src/layouts/payment/payment.vue
Normal file
347
src/layouts/payment/payment.vue
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal notFullScreen ref="loadingModal" />
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" v-if="!isPaypal" />
|
||||
<funnelSubHeader
|
||||
class="mb-5"
|
||||
cmsWidgetName="FunnelSubHeaderWidget"
|
||||
leftAlignHeader
|
||||
v-if="!isPaypal" />
|
||||
<hr class="mt-0" />
|
||||
<div id="card-container">
|
||||
<iframe
|
||||
ref="paymentFrame"
|
||||
class="hop-iframe payment-iframe"
|
||||
name="card-frame"
|
||||
id="card-frame"
|
||||
seamless
|
||||
scrolling="no">
|
||||
</iframe>
|
||||
</div>
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<form
|
||||
ref="myForm"
|
||||
id="card-data"
|
||||
:target="paypalPayment ? '_top' : 'card-frame'"
|
||||
method="POST"
|
||||
:action="checkoutUrl">
|
||||
<input type="hidden" name="paymentType" :value="paymentType" />
|
||||
<input type="hidden" name="sgSessionId" :value="authToken" />
|
||||
<input type="hidden" name="sgAuthToken" :value="authToken" />
|
||||
<input type="hidden" name="sgSignaturePublic" :value="authSignature" />
|
||||
<input type="hidden" name="sgSignatureStartDate" :value="authSignatureStart" />
|
||||
<input type="hidden" name="referralSeqNum" :value="referralSequenceNumber" />
|
||||
|
||||
<input type="hidden" name="sge_commerce_indicator_isinternet" value="true" />
|
||||
<input type="hidden" name="sghopsource" value="Safelite.com" />
|
||||
<input type="hidden" name="sgHtmlStyle" value="ResourceSafeliteHtml" />
|
||||
<input type="hidden" name="sgErrorMessagesEmbedded" value="true" />
|
||||
|
||||
<input type="hidden" name="sgNoKeystrokeProcessing" value="false" />
|
||||
|
||||
<input type="hidden" name="maskCharacter" value="*" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="styleSheetCode"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/css/experiments/site-2020Styling.css" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="styleSheetCode2"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/css/experiments/SLHop-2020Styling.css" />
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgReceiptResponseURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment/Success.aspx" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgDeclineResponseURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment/Cancel.aspx" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgErrorResponseURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment/Error.aspx" />
|
||||
|
||||
<input type="hidden" name="sgheaderline1" :value="sgHeaderline1" />
|
||||
<input type="hidden" name="sgHeader1subtitle" value="" />
|
||||
<input type="hidden" name="sgheaderline2" :value="sgHeaderline2" />
|
||||
<input type="hidden" name="sgheaderline3" value="" />
|
||||
<input type="hidden" name="sgheaderline4" value="" />
|
||||
<input type="hidden" name="sgheaderline5" value="*Required information" />
|
||||
|
||||
<input type="hidden" name="billTo_firstName" :value="firstName" />
|
||||
<input type="hidden" name="billTo_firstNameShow" value="true" />
|
||||
<input type="hidden" name="sghopmiddleInitialShow" value="false" />
|
||||
<input type="hidden" name="billTo_lastName" :value="lastName" />
|
||||
<input type="hidden" name="billTo_lastNameShow" value="true" />
|
||||
|
||||
<input type="hidden" name="billTo_street1" value="" />
|
||||
<input type="hidden" name="billTo_street2" value="" />
|
||||
<input type="hidden" name="billTo_city" value="" />
|
||||
<input type="hidden" name="billTo_state" value="" />
|
||||
|
||||
<input type="hidden" name="billTo_postalCode" value="" />
|
||||
<input type="hidden" name="billTo_postalCodeShow" value="true" />
|
||||
<input type="hidden" name="billTo_postalCodeEnable" value="true" />
|
||||
<input type="hidden" name="sgLabelPostalCode" value="Billing ZIP" />
|
||||
|
||||
<input type="hidden" name="sgtotalamount" :value="displayAmount" />
|
||||
<input type="hidden" name="totalAmountDecimal" :value="totalAmount" />
|
||||
<input type="hidden" name="lineItems" :value="lineItems" />
|
||||
|
||||
<input type="hidden" name="sgdiscountstrikethruamount" value="" />
|
||||
|
||||
<input type="hidden" name="callerDisplayText" value="" />
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgtermsofusedisplaytext"
|
||||
value="By selecting submit, I agree to Safelite's" />
|
||||
<input type="hidden" name="sgtermsofuseurl" value="http://www.safelite.com/terms-of-use/" />
|
||||
<input type="hidden" name="sgtermsofuselinkdisplaytext" value="terms of use" />
|
||||
<input type="hidden" name="sgtermsofcancelrefunddisplaytext" value="and" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgtermsofcancelrefundlinkdisplaytext"
|
||||
value="cancellation and refund policy" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgtermsofcancelrefundurl"
|
||||
value="http://www.safelite.com/cancellation-refund-policy" />
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgErrorMessageCardVerification"
|
||||
value="Credit card could not be processed" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgErrorMessageTimeOut"
|
||||
value="For your security, this transaction has been timed out." />
|
||||
|
||||
<input type="hidden" name="sgLabelCardNumber" value="" />
|
||||
<input type="hidden" name="sgLabelAddressLine1" :value="address1" />
|
||||
<input type="hidden" name="sgLabelAddressLine2" :value="address2" />
|
||||
<input type="hidden" name="sgLabelCity" :value="city" />
|
||||
<input type="hidden" name="sgLabelState" :value="state" />
|
||||
|
||||
<input type="hidden" name="sgCtu" :value="ctu" />
|
||||
<input type="hidden" name="sgWorkOrder" :value="workOrderNumber" />
|
||||
<input type="hidden" name="sgEmailAddress" :value="emailAddress" />
|
||||
<input type="hidden" name="paypalInvoiceNumber" :value="invoiceNumber" />
|
||||
<input type="hidden" name="payPalSuccessUrl" :value="payPalSuccessUrl" />
|
||||
<input type="hidden" name="paypalCancelUrl" :value="payPalCancelUrl" />
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgCCDeclineURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment.aspx?error=ccDecline" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="sgCCTimeoutURL"
|
||||
value="https://fixmyglasstest.safelite.com/FixMyGlass/Payment.aspx?error=ccTimeout" />
|
||||
|
||||
<input type="hidden" name="sgTransactionType" value="authorization" />
|
||||
<input type="hidden" name="amount" :value="totalAmount" />
|
||||
<input type="hidden" name="ctu" :value="ctu" />
|
||||
<input type="hidden" name="orderNumber" :value="workOrderNumber" />
|
||||
|
||||
<input type="hidden" name="billTo_email" :value="emailAddress" />
|
||||
<input type="hidden" name="useDecisionManager" value="True" />
|
||||
<input type="hidden" name="correlationId" :value="referralCorrelationId" />
|
||||
<input type="hidden" name="sgErrorMessageCard_CVN" value="Please enter a valid CVV" />
|
||||
|
||||
<input type="hidden" name="ship_to_address_line1" :value="address1" />
|
||||
<input type="hidden" name="ship_to_address_line2" :value="address2" />
|
||||
<input type="hidden" name="ship_to_address_city" :value="city" />
|
||||
<input type="hidden" name="ship_to_address_state" :value="state" />
|
||||
<input type="hidden" name="ship_to_address_country" value="US" />
|
||||
<input type="hidden" name="ship_to_address_postal_code" :value="zipCode" />
|
||||
<input type="hidden" name="ship_to_phone" :value="phoneNumber" />
|
||||
|
||||
<input type="hidden" name="calling_application" value="NextGen" />
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import store from "@/store";
|
||||
import { externalUrls } from "@/router/router-constants/externalUrl-values";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
|
||||
// Validation
|
||||
import { Form } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "payment",
|
||||
data() {
|
||||
return {
|
||||
checkoutUrl: externalUrls.SAFELITE_HOP,
|
||||
payPalSuccessUrl: externalUrls.PAYPAL_SUCCESS_URL,
|
||||
payPalCancelUrl: externalUrls.PAYPAL_CANCEL_URL,
|
||||
paymentType: this.getPaymentType(),
|
||||
authToken: "",
|
||||
authSignature: "",
|
||||
authSignatureStart: "",
|
||||
referralSequenceNumber: store.getters.order.referralSequenceNumber,
|
||||
emailAddress: store.getters.order.customer.emailAddress,
|
||||
address1: this.getAddress1(),
|
||||
address2: this.getAddress2(),
|
||||
city: this.getCity(),
|
||||
state: this.getState(),
|
||||
zipCode: this.getZipCode(),
|
||||
firstName: store.getters.order.customer.firstName,
|
||||
lastName: store.getters.order.customer.lastName,
|
||||
phoneNumber: store.getters.order.customer.phoneNumber,
|
||||
ctu: store.getters.order.serviceLocation.zipCodeCtu,
|
||||
referralCorrelationId: store.getters.order.referralCorrelationId,
|
||||
// TODO: replace these
|
||||
workOrderNumber: Math.floor(Math.random() * 90000) + 10000,
|
||||
invoiceNumber: Math.floor(Math.random() * 90000) + 10000,
|
||||
totalAmount: "321.00", // TODO: get from cart?
|
||||
displayAmount: "$321.00", // TODO: get from cart?
|
||||
lineItems: "Labor|189.99|1||Repair supplies|7.99|1",
|
||||
sgHeaderline1: this.getHeaderLine1(),
|
||||
sgHeaderline2: this.getHeaderLine2(),
|
||||
paypalPayment: this.isPaypal(),
|
||||
};
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
// TODO: Data fetching Promise declarations
|
||||
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next(async (vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.fetchSignatureInfo();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// TODO
|
||||
return true;
|
||||
},
|
||||
getAddress1() {
|
||||
return store.getters.order.serviceLocation.address
|
||||
? store.getters.order.serviceLocation.address
|
||||
: store.getters.order.serviceLocation.provider.address.streetAddress;
|
||||
},
|
||||
getAddress2() {
|
||||
return store.getters.order.serviceLocation.address2
|
||||
? store.getters.order.serviceLocation.address2
|
||||
: null;
|
||||
},
|
||||
getCity() {
|
||||
return store.getters.order.serviceLocation.city
|
||||
? store.getters.order.serviceLocation.city
|
||||
: store.getters.order.serviceLocation.provider.address.city;
|
||||
},
|
||||
getState() {
|
||||
return store.getters.order.serviceLocation.state
|
||||
? store.getters.order.serviceLocation.state
|
||||
: store.getters.order.serviceLocation.provider.address.state;
|
||||
},
|
||||
getZipCode() {
|
||||
return store.getters.order.serviceLocation.zipCode
|
||||
? store.getters.order.serviceLocation.zipCode
|
||||
: store.getters.order.serviceLocation.provider.address.zipCode;
|
||||
},
|
||||
getPaymentType() {
|
||||
return store.getters.order.payment.payNowType;
|
||||
},
|
||||
getHeaderLine1() {
|
||||
if (!this.isPaypal()) {
|
||||
return "Submit payment information";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
getHeaderLine2() {
|
||||
if (!this.isPaypal()) {
|
||||
return "Save time when you pay for your service today! Please select a payment option.";
|
||||
}
|
||||
return "";
|
||||
},
|
||||
isPaypal() {
|
||||
if (this.getPaymentType() == "pp") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
backButtonAction() {
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
forwardButtonAction() {},
|
||||
async fetchSignatureInfo() {
|
||||
if (this.isPaypal()) {
|
||||
this.$refs.loadingModal.showModal();
|
||||
}
|
||||
const signatureInfo = await this.dispatchStoreAction(storeActions.GET_SIGNATURE);
|
||||
this.authToken = signatureInfo.data.token;
|
||||
this.authSignature = signatureInfo.data.signature;
|
||||
this.authSignatureStart = signatureInfo.data.startDate;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.myForm.submit();
|
||||
|
||||
if (this.paymentType == "ap") {
|
||||
const iframe = this.$refs.paymentFrame;
|
||||
if (iframe) {
|
||||
iframe.onload = () => {
|
||||
if (iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage("afterpay", "*");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.payment-iframe {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hop-iframe {
|
||||
position: relative;
|
||||
min-height: 1300px;
|
||||
width: 100%;
|
||||
border: 0 none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -36,6 +36,10 @@ jest.mock("@/mixins/base-mixin", () => ({
|
|||
if (action === mockPriceOrderStoreAction) return items;
|
||||
else return mockStoreActionResults[action];
|
||||
},
|
||||
dispatchStoreActionWithLogging(action, items, pageName, encode) {
|
||||
if (action === mockPriceOrderStoreAction) return items;
|
||||
else return mockStoreActionResults[action];
|
||||
},
|
||||
getTierOnePackagePrice() {
|
||||
return mockTierOnePrice;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,45 +1,56 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<cashOrInsuranceQuestion
|
||||
ref="cashOrInsurance"
|
||||
cmsWidgetName="CashOrInsuranceQuestionWidget"
|
||||
v-model="isInsuranceSelected"
|
||||
groupName="CashOrInsuranceQuestion" />
|
||||
<servicePackageQuestion
|
||||
ref="servicePackage"
|
||||
cashCmsWidgetName="CashServicePackageQuestionWidget"
|
||||
insuranceCmsWidgetName="InsuranceServicePackageQuestionWidget"
|
||||
groupName="ServicePackageQuestion"
|
||||
:availableLineItems="availableLineItems"
|
||||
:isInsuranceSelected="isInsuranceSelected"
|
||||
@vapsItemsSelected="vapsItemsSelectedAction"
|
||||
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
||||
validationRules="option-required"
|
||||
isRequired />
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<textBlock
|
||||
cmsWidgetName="quoteDisclaimer"
|
||||
justifyText="left"
|
||||
typeStyle="caption"
|
||||
class="mt-4" />
|
||||
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
|
||||
<contentGroupModal ref="FrontWiperModal" cmsWidgetName="FrontWiperModal" />
|
||||
<contentGroupModal ref="RearWiperModal" cmsWidgetName="RearWiperModal" />
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<cashOrInsuranceQuestion
|
||||
ref="cashOrInsurance"
|
||||
cmsWidgetName="CashOrInsuranceQuestionWidget"
|
||||
v-model="isInsuranceSelected"
|
||||
groupName="CashOrInsuranceQuestion" />
|
||||
|
||||
<servicePackageQuestion
|
||||
ref="servicePackage"
|
||||
cashCmsWidgetName="CashServicePackageQuestionWidget"
|
||||
insuranceCmsWidgetName="InsuranceServicePackageQuestionWidget"
|
||||
groupName="ServicePackageQuestion"
|
||||
:availableLineItems="availableLineItems"
|
||||
:isInsuranceSelected="isInsuranceSelected"
|
||||
@vapsItemsSelected="vapsItemsSelectedAction"
|
||||
v-on="{ 'buttonEvent.openModal': openModalAction }"
|
||||
validationRules="option-required"
|
||||
isRequired />
|
||||
|
||||
<textBlock
|
||||
cmsWidgetName="quoteDisclaimer"
|
||||
justifyText="left"
|
||||
typeStyle="caption"
|
||||
class="mt-4" />
|
||||
|
||||
<contentGroupModal ref="RainDefenseModal" cmsWidgetName="RainDefenseModal" />
|
||||
<contentGroupModal ref="FrontWiperModal" cmsWidgetName="FrontWiperModal" />
|
||||
<contentGroupModal ref="RearWiperModal" cmsWidgetName="RearWiperModal" />
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -48,7 +59,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import cashOrInsuranceQuestion from "./cash-or-insurance-question/cash-or-insurance-question";
|
||||
|
|
@ -75,12 +86,20 @@ export default {
|
|||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreAction(storeActions.GET_WIPERS);
|
||||
const rainDefensePromise = baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.GET_RAIN_DEFENSE
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_WIPERS,
|
||||
null,
|
||||
"quote"
|
||||
);
|
||||
const supportingItemsPromise = baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.GET_SUPPORTING_ITEMS
|
||||
const rainDefensePromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_RAIN_DEFENSE,
|
||||
null,
|
||||
"quote"
|
||||
);
|
||||
const supportingItemsPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SUPPORTING_ITEMS,
|
||||
null,
|
||||
"quote"
|
||||
);
|
||||
|
||||
const promiseResultMap = [
|
||||
|
|
@ -113,14 +132,20 @@ export default {
|
|||
...clonedGlassParts,
|
||||
];
|
||||
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreAction(
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
availableLineItems: availableLineItems,
|
||||
},
|
||||
"quote",
|
||||
false
|
||||
);
|
||||
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_SUPPORTING_ITEMS,
|
||||
resultMap.supportingItems,
|
||||
false
|
||||
);
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
|
|
@ -213,17 +238,15 @@ export default {
|
|||
);
|
||||
}
|
||||
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS,
|
||||
this.supportingItems,
|
||||
false
|
||||
);
|
||||
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_VAPS, this.selectedVaps, false);
|
||||
|
||||
const payment = this.$store.getters.payment;
|
||||
if (payment.isInsurance) {
|
||||
navigateToHeritageFunnel({ loadingModal: this.$refs.loadingModal });
|
||||
navigateToHeritageFunnel({
|
||||
shouldSaveSession: true,
|
||||
pageNameToLog: "quote",
|
||||
loadingModal: this.$refs.loadingModal,
|
||||
});
|
||||
} else {
|
||||
this.$router.navigateWithSaving(
|
||||
this.navigationScenarios.CLICKED_FORWARD_WITH_CASH,
|
||||
|
|
@ -234,7 +257,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
|
|
|
|||
|
|
@ -31,9 +31,15 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
loadInitialData() {
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreAction(storeActions.GET_WIPERS);
|
||||
const rainDefensePromise = baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.GET_RAIN_DEFENSE
|
||||
const wipersPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_WIPERS,
|
||||
null,
|
||||
"review"
|
||||
);
|
||||
const rainDefensePromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_RAIN_DEFENSE,
|
||||
null,
|
||||
"review"
|
||||
);
|
||||
|
||||
const promiseResultMap = [
|
||||
|
|
|
|||
|
|
@ -1,105 +1,113 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<!-- When customer-details is added: v-slot="{ meta }" -->
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<textBlock
|
||||
:customText="subHeaderTitle"
|
||||
typeStyle="h5"
|
||||
justifyText="center"
|
||||
margin="mt-1"
|
||||
class="dark-header" />
|
||||
<textBlock
|
||||
:customText="subHeaderBody"
|
||||
typeStyle="body"
|
||||
justifyText="left"
|
||||
margin="mt-0 mb-2" />
|
||||
|
||||
<buttonMain
|
||||
ref="buttonMain"
|
||||
isPrimary
|
||||
:buttonText="forwardButtonText"
|
||||
loaderColor="white"
|
||||
class="mb-2"
|
||||
@click-event="forwardButtonAction" />
|
||||
|
||||
<div>
|
||||
<hr />
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<textBlock
|
||||
customText="Appointment Details"
|
||||
typeStyle="label bold"
|
||||
justifyText="justify-text-left"
|
||||
margin="mt-0"
|
||||
class="dark-header" />
|
||||
<textBlock
|
||||
:customText="subHeaderTitle"
|
||||
typeStyle="h5"
|
||||
justifyText="center"
|
||||
margin="mt-1"
|
||||
class="dark-header" />
|
||||
<textBlock
|
||||
:customText="subHeaderBody"
|
||||
typeStyle="body"
|
||||
justifyText="left"
|
||||
margin="mt-0 mb-2" />
|
||||
|
||||
<div class="px-4">
|
||||
<vehicleReview
|
||||
cmsWidgetName="VehicleReviewWidget"
|
||||
:vehicle="vehicleInfo"
|
||||
@edit-clicked="editVehicle" />
|
||||
<buttonMain
|
||||
ref="buttonMain"
|
||||
isPrimary
|
||||
:buttonText="forwardButtonText"
|
||||
loaderColor="white"
|
||||
class="mb-2"
|
||||
@click-event="forwardButtonAction" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<div>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<damageReview
|
||||
cmsWidgetName="DamageReviewWidget"
|
||||
damageLocationsWidgetName="DamageLocationsWidget"
|
||||
:damage="damageInfo"
|
||||
@edit-clicked="editDamage" />
|
||||
<textBlock
|
||||
customText="Appointment Details"
|
||||
typeStyle="label bold"
|
||||
justifyText="justify-text-left"
|
||||
margin="mt-0"
|
||||
class="dark-header" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<div class="px-4">
|
||||
<vehicleReview
|
||||
cmsWidgetName="VehicleReviewWidget"
|
||||
:vehicle="vehicleInfo"
|
||||
@edit-clicked="editVehicle" />
|
||||
|
||||
<servicePackageReview
|
||||
ref="servicePackageReview"
|
||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
defaultPackageItemsCmsName="DefaultPackageItemDescriptions"
|
||||
vapsItemsCmsName="VapsItemDescriptions"
|
||||
:damage="damageInfo"
|
||||
:lineItems="lineItems"
|
||||
@edit-clicked="editServicePackage" />
|
||||
<hr class="my-0" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<damageReview
|
||||
cmsWidgetName="DamageReviewWidget"
|
||||
damageLocationsWidgetName="DamageLocationsWidget"
|
||||
:damage="damageInfo"
|
||||
@edit-clicked="editDamage" />
|
||||
|
||||
<serviceLocationReview
|
||||
cmsWidgetName="ServiceLocationTitleWidget"
|
||||
:serviceLocation="serviceLocationInfo"
|
||||
@edit-clicked="editServiceLocation" />
|
||||
<hr class="my-0" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<servicePackageReview
|
||||
ref="servicePackageReview"
|
||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
defaultPackageItemsCmsName="DefaultPackageItemDescriptions"
|
||||
vapsItemsCmsName="VapsItemDescriptions"
|
||||
:damage="damageInfo"
|
||||
:lineItems="lineItems"
|
||||
@edit-clicked="editServicePackage" />
|
||||
|
||||
<scheduleReview
|
||||
cmsWidgetName="ScheduleWidget"
|
||||
:appointmentType="appointmentType"
|
||||
@edit-clicked="editSchedule" />
|
||||
<hr class="my-0" />
|
||||
|
||||
<hr class="my-0" />
|
||||
<serviceLocationReview
|
||||
cmsWidgetName="ServiceLocationTitleWidget"
|
||||
:serviceLocation="serviceLocationInfo"
|
||||
@edit-clicked="editServiceLocation" />
|
||||
|
||||
<customerReview
|
||||
cmsWidgetName="CustomerReviewWidget"
|
||||
:customer="customerInfo"
|
||||
@edit-clicked="editCustomerDetails" />
|
||||
<hr class="my-0" />
|
||||
|
||||
<scheduleReview
|
||||
cmsWidgetName="ScheduleWidget"
|
||||
:appointmentType="appointmentType"
|
||||
@edit-clicked="editSchedule" />
|
||||
|
||||
<hr class="my-0" />
|
||||
|
||||
<customerReview
|
||||
cmsWidgetName="CustomerReviewWidget"
|
||||
:customer="customerInfo"
|
||||
@edit-clicked="editCustomerDetails" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<hr class="my-0" />
|
||||
</div>
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<hr class="my-0" />
|
||||
</div>
|
||||
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import buttonMain from "@/ux-components/button-main/button-main";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
|
|
@ -117,6 +125,9 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
|||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import store from "@/store";
|
||||
|
||||
// Validation
|
||||
import { Form } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "review",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -223,7 +234,12 @@ export default {
|
|||
backButtonAction() {
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
forwardButtonAction() {},
|
||||
forwardButtonAction() {
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_FORWARD,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
editVehicle() {
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_VEHICLE_EDIT,
|
||||
|
|
@ -292,7 +308,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
buttonMain,
|
||||
textBlock,
|
||||
|
|
@ -302,6 +318,7 @@ export default {
|
|||
serviceLocationReview,
|
||||
scheduleReview,
|
||||
customerReview,
|
||||
Form,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ import { storeActions } from "@/constants/store-actions";
|
|||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
export async function getAlertReasons(ctu) {
|
||||
const alertReasons = await baseMixin.methods.dispatchStoreAction(
|
||||
const alertReasons = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_ALERT_REASONS_BY_CTU,
|
||||
{
|
||||
ctu: ctu,
|
||||
}
|
||||
},
|
||||
"schedule"
|
||||
);
|
||||
|
||||
return Promise.resolve(alertReasons);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,65 @@ jest.mock("@/mixins/base-mixin.js", () => ({
|
|||
]);
|
||||
}
|
||||
}),
|
||||
dispatchStoreActionWithLogging: jest.fn().mockImplementation((storeAction) => {
|
||||
if (storeAction === "getShopTimeSlots") {
|
||||
return {
|
||||
data: {
|
||||
estimatedServiceMinutesMinimum: 90,
|
||||
estimatedServiceMinutesMaximum: 120,
|
||||
days: [
|
||||
{
|
||||
date: "2023-12-01",
|
||||
timeSlots: [
|
||||
{
|
||||
id: "06747-01820-S-B*20424*7 AM",
|
||||
startTime: "07:00",
|
||||
endTime: "08:00",
|
||||
offerPremium: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
if (storeAction === "getMobilePremiumFee") {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
partNumber: "EARLY BIRD",
|
||||
description: null,
|
||||
partType: "EARLY BIRD",
|
||||
laborAmount: 0,
|
||||
sellingPrice: 14.99,
|
||||
kitPrice: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (storeAction === "priceOrderItemsAndSaveServerData") {
|
||||
return Promise.resolve([
|
||||
{
|
||||
partNumber: "EARLY BIRD",
|
||||
description: null,
|
||||
partType: "EARLY BIRD",
|
||||
laborAmount: 0,
|
||||
sellingPrice: 14.99,
|
||||
kitPrice: 0,
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (storeAction === "saveSupportingItemsSuppressingStateResetting") {
|
||||
return Promise.resolve([
|
||||
{
|
||||
partNumber: "EARLY BIRD",
|
||||
description: null,
|
||||
partType: "EARLY BIRD",
|
||||
laborAmount: 0,
|
||||
sellingPrice: 14.99,
|
||||
kitPrice: 0,
|
||||
},
|
||||
]);
|
||||
}
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
@ -226,10 +285,11 @@ describe("schedule.vue...", () => {
|
|||
);
|
||||
|
||||
// Assert
|
||||
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledTimes(3);
|
||||
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledTimes(3);
|
||||
expect(baseMixin.methods.dispatchStoreActionWithLogging).toHaveBeenCalledWith(
|
||||
"getShopTimeSlots",
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
|
@ -623,7 +683,7 @@ function setupMocks({ customMountOptions }) {
|
|||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.$refs.datePicker.initializeComponent = jest.fn();
|
||||
wrapper.vm.$refs.locationAlerts.initializeComponent = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.timeSlotModalQuestion.openModal = jest.fn();
|
||||
|
||||
return { wrapper };
|
||||
|
|
|
|||
|
|
@ -1,50 +1,62 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
<template v-if="ChangeShopLink.length">
|
||||
<textBlock
|
||||
cmsWidgetName="ChangeShopLink"
|
||||
justifyText="center"
|
||||
class="mb-3 text-link-small"
|
||||
marginTopSizeOverride="1" />
|
||||
</template>
|
||||
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
|
||||
<datePicker
|
||||
customComponentId="dateQuestion"
|
||||
selectableDatesSetting="custom"
|
||||
ref="datePicker"
|
||||
v-model="selectedDate"
|
||||
class="text-link-small"
|
||||
:customSelectableDatesCallback="getAvailableDatesMethod"
|
||||
validationRules="date-required"
|
||||
@date-clicked="openInshopTimeSlotsModal" />
|
||||
<timeSlotModalQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
customComponentId="timeSlotModalQuestion"
|
||||
v-model="selectedTimeSlotInfo"
|
||||
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"
|
||||
@time-slot-modal-closed="timeSlotModalClosed"
|
||||
validationRules="time-slot-selection-required" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
<template v-if="ChangeShopLink.length">
|
||||
<textBlock
|
||||
cmsWidgetName="ChangeShopLink"
|
||||
justifyText="center"
|
||||
class="mb-3 text-link-small"
|
||||
marginTopSizeOverride="1" />
|
||||
</template>
|
||||
<locationAlerts cmsWidgetPrefix="LocationAlert-" ref="locationAlerts" />
|
||||
<datePicker
|
||||
customComponentId="dateQuestion"
|
||||
selectableDatesSetting="custom"
|
||||
ref="datePicker"
|
||||
v-model="selectedDate"
|
||||
class="text-link-small"
|
||||
:customSelectableDatesCallback="getAvailableDatesMethod"
|
||||
validationRules="date-required"
|
||||
@date-clicked="openInshopTimeSlotsModal" />
|
||||
<timeSlotModalQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
customComponentId="timeSlotModalQuestion"
|
||||
v-model="selectedTimeSlotInfo"
|
||||
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
|
||||
"
|
||||
@time-slot-modal-closed="timeSlotModalClosed"
|
||||
validationRules="time-slot-selection-required" />
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
|
@ -52,7 +64,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
|
|
@ -156,9 +168,10 @@ const getAvailableDates = async (
|
|||
const makeParallelCalls = async () => {
|
||||
await Promise.all(
|
||||
storeActionConfigs.map(async (storeAction) => {
|
||||
const timeSlotsResponse = await baseMixin.methods.dispatchStoreAction(
|
||||
const timeSlotsResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeAction.storeAction,
|
||||
storeAction.payload,
|
||||
"schedule",
|
||||
false
|
||||
);
|
||||
timeSlotsResponsesData.estimatedServiceMinutesMinimum =
|
||||
|
|
@ -206,17 +219,20 @@ export default {
|
|||
preSelectedDate: preSelectedDate,
|
||||
});
|
||||
|
||||
const premiumFeePromise = baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.GET_MOBILE_PREMIUM_FEE
|
||||
const premiumFeePromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_MOBILE_PREMIUM_FEE,
|
||||
null,
|
||||
"schedule"
|
||||
);
|
||||
|
||||
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
|
||||
if (result.data) {
|
||||
return baseMixin.methods.dispatchStoreAction(
|
||||
return baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
availableLineItems: [result.data],
|
||||
},
|
||||
"schedule",
|
||||
false
|
||||
);
|
||||
} else {
|
||||
|
|
@ -352,23 +368,23 @@ export default {
|
|||
}
|
||||
},
|
||||
updateFooterButtonText(timeSlotInfo) {
|
||||
let funnelFooterButtonText;
|
||||
let navbarButtonText;
|
||||
if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) {
|
||||
funnelFooterButtonText = "Continue";
|
||||
navbarButtonText = "Continue";
|
||||
} else {
|
||||
funnelFooterButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(
|
||||
navbarButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(
|
||||
timeSlotInfo.timeSlot.date
|
||||
)}`;
|
||||
|
||||
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||
funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlotInfo.timeSlot.startTime
|
||||
)}`;
|
||||
} else if (
|
||||
this.appointmentType === AppointmentTypeStrings.MOBILE &&
|
||||
!timeSlotInfo.isPremiumAppointment
|
||||
) {
|
||||
funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
navbarButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlotInfo.timeSlot.startTime,
|
||||
true
|
||||
)} - ${this.getDisplayTextForMilitaryTime(
|
||||
|
|
@ -377,7 +393,7 @@ export default {
|
|||
)}`;
|
||||
}
|
||||
}
|
||||
this.$refs.funnelFooter.updateButtonText(funnelFooterButtonText);
|
||||
this.$refs.navbar.updateButtonText(navbarButtonText);
|
||||
},
|
||||
convertSelectedDateToShortMonthAndDay(selectedDate) {
|
||||
// This conversion ensures we don't get get GMT induced date changes
|
||||
|
|
@ -483,7 +499,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
|
|
|
|||
|
|
@ -516,9 +516,6 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
.time-slots-modal.modal.modal-component {
|
||||
> .modal-dialog > .modal-content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.modal-header {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0 !important;
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@ export default {
|
|||
<style lang="scss">
|
||||
.appointment-type-question {
|
||||
.list-card img {
|
||||
height: auto;
|
||||
width: 3.417rem;
|
||||
height: 1.5rem;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,42 +1,45 @@
|
|||
import { storeActions } from "@/constants/store-actions";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
export async function getPricedMobileFeePart(serviceZipCode) {
|
||||
export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
|
||||
if (!serviceZipCode) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const zipCodeData = await baseMixin.methods.getZipCodeData(serviceZipCode);
|
||||
const zipCodeData = await baseMixin.methods.getZipCodeData(serviceZipCode, pageNameToLog);
|
||||
|
||||
// Get the Mobile Fee Part
|
||||
const mobileFeePart = await baseMixin.methods.dispatchStoreAction(
|
||||
const mobileFeePart = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_MOBILE_FEE_PART,
|
||||
null,
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
|
||||
// Get the Mobile Fee Part Price
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreAction(
|
||||
const pricingResults = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
||||
{
|
||||
availableLineItems: [mobileFeePart.data],
|
||||
serviceZipCode: serviceZipCode,
|
||||
serviceZipCodeCtu: zipCodeData.zipCodeCtu,
|
||||
},
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
|
||||
return Promise.resolve(pricingResults[0]);
|
||||
}
|
||||
|
||||
export async function getServiceabilityDetails(serviceZipCode, lineItems) {
|
||||
export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNameToLog) {
|
||||
// Get the Mobile Fee Part
|
||||
const serviceabilityDetails = await baseMixin.methods.dispatchStoreAction(
|
||||
const serviceabilityDetails = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SERVICEABILITY_DETAILS,
|
||||
{
|
||||
serviceZipCode: serviceZipCode,
|
||||
lineItems: lineItems,
|
||||
},
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
|
||||
|
|
@ -50,7 +53,7 @@ export async function getAvailabilityRating(
|
|||
providerNumber
|
||||
) {
|
||||
// For a given shop provider number and date range, get the appointment time slots available
|
||||
const shopTimeSlots = await baseMixin.methods.dispatchStoreAction(
|
||||
const shopTimeSlots = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SHOP_TIME_SLOTS,
|
||||
{
|
||||
providerNumber: providerNumber,
|
||||
|
|
@ -58,6 +61,7 @@ export async function getAvailabilityRating(
|
|||
endDate: endDate,
|
||||
shopAppointmentType: shopAppointmentType,
|
||||
},
|
||||
"service-location",
|
||||
false
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -188,6 +188,47 @@ jest.mock("@/mixins/base-mixin.js", () => ({
|
|||
});
|
||||
}
|
||||
|
||||
if (actionName === mockStoreActionGetShopTimeSlots) {
|
||||
if (request.providerNumber == "0000001") {
|
||||
return Promise.resolve(mockGetShopTimeSlotsGoodAvailability);
|
||||
}
|
||||
|
||||
return Promise.resolve(mockGetShopTimeSlotsLowAvailability);
|
||||
}
|
||||
}),
|
||||
dispatchStoreActionWithLogging: jest.fn().mockImplementation((actionName, request) => {
|
||||
if (actionName === mockStoreActionGetMobileFeePart) {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
partNumber: "MOBILE FEE",
|
||||
description: "MOBILE FEE",
|
||||
partType: "FEE",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (actionName === mockStoreActionPriceOrderItemsAndSaveServerData) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
partNumber: "MOBILE FEE",
|
||||
description: "MOBILE FEE",
|
||||
partType: "FEE",
|
||||
laborAmount: 49.99,
|
||||
sellingPrice: 0,
|
||||
kitPrice: 0,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if (actionName === mockStoreActionGetServiceabilityDetails) {
|
||||
return Promise.resolve({
|
||||
isGlassServiceableInshop: true,
|
||||
isRecalibrationServiceableInshop: true,
|
||||
isGlassServiceableMobile: true,
|
||||
isRecalibrationServiceableMobile: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (actionName === mockStoreActionGetShopTimeSlots) {
|
||||
if (request.providerNumber == "0000001") {
|
||||
return Promise.resolve(mockGetShopTimeSlotsGoodAvailability);
|
||||
|
|
|
|||
|
|
@ -229,7 +229,8 @@ export default {
|
|||
) {
|
||||
// Validate the Zip Code
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.addressQuestions.zipCode
|
||||
this.internalModel.addressQuestions.zipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
if (!zipCodeData.isValid) {
|
||||
|
|
@ -238,10 +239,17 @@ export default {
|
|||
} else {
|
||||
// retrieve mobile fee part
|
||||
const serviceZipCode = this.internalModel.addressQuestions.zipCode;
|
||||
const mobileFeePart = await getPricedMobileFeePart(serviceZipCode);
|
||||
const mobileFeePart = await getPricedMobileFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode);
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// update content related to service zip code
|
||||
this.$emit("updated-mobile-fee-part", mobileFeePart);
|
||||
|
|
|
|||
|
|
@ -1,88 +1,113 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
<serviceZipModalQuestion
|
||||
v-model="serviceZipCodeQuestion"
|
||||
ref="serviceZipCodeQuestion"
|
||||
:mobileFeePart="mobileFeePart"
|
||||
@updated-mobile-fee-part="setMobileFeePart"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
linkWidgetName="ServiceZipLinkWidget"
|
||||
modalWidgetName="ServiceZipModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
<alert
|
||||
ref="alertMilitaryBaseZip"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertMilitaryBaseZipWidget"
|
||||
v-if="displayMilitaryZipAlert"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
ref="alertMobileOnly"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertMobileOnlyWidget"
|
||||
v-if="displayServiceableMobileOnly"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
ref="alertRecalNoMobile"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertRecalNoMobileWidget"
|
||||
v-if="displayRecalibrationWarning"
|
||||
@text-link-clicked="openRecalibrationInformationModal"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
ref="alertInshopOnly"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertInshopOnlyWidget"
|
||||
v-if="displayServiceableInshopOnly"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
ref="alertNoShops"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertNoShopsWidget"
|
||||
v-if="displayNoShopsAlert"
|
||||
alertClass="alert-warning" />
|
||||
<appointmentTypeQuestion
|
||||
v-model="selectedAppointmentType"
|
||||
v-show="isAppointmentTypeDisplayed"
|
||||
:isServiceableMobile="isServiceableMobile"
|
||||
:isServiceableInshop="isServiceableInshop"
|
||||
:isDisplayed="isAppointmentTypeDisplayed"
|
||||
ref="appointmentTypeQuestion"
|
||||
groupName="appointmentTypeQuestion"
|
||||
cmsWidgetName="AppointmentTypeQuestionWidget"
|
||||
validationRules="option-required" />
|
||||
<mobileLocationModalQuestions
|
||||
customComponentId="mobileLocationQuestions"
|
||||
v-if="selectedAppointmentType === 'Mobile'"
|
||||
v-model="mobileLocationQuestions"
|
||||
:mobileFeePart="mobileFeePart"
|
||||
@updated-mobile-fee-part="setMobileFeePart"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
validationRules="mobile-location-required"
|
||||
ref="mobileLocationQuestions"
|
||||
linkWidgetName="MobileLocationLinkWidget"
|
||||
modalWidgetName="MobileLocationModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
<shopQuestion
|
||||
ref="shopQuestion"
|
||||
v-show="isShopQuestionDisplayed"
|
||||
v-model="selectedProvider"
|
||||
:selectedAppointmentType="selectedAppointmentType"
|
||||
:isDisplayed="isShopQuestionDisplayed"
|
||||
@updated-shop-list="setUpdatedShopList"
|
||||
cmsWidgetName="ShopQuestionWidget" />
|
||||
<contentGroupModal ref="recalibrationInformationModal" cmsWidgetName="RecalModal" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid || displayNoShopsAlert"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
|
||||
<serviceZipModalQuestion
|
||||
v-model="serviceZipCodeQuestion"
|
||||
ref="serviceZipCodeQuestion"
|
||||
:mobileFeePart="mobileFeePart"
|
||||
@updated-mobile-fee-part="setMobileFeePart"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
linkWidgetName="ServiceZipLinkWidget"
|
||||
modalWidgetName="ServiceZipModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
|
||||
<alert
|
||||
ref="alertMilitaryBaseZip"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertMilitaryBaseZipWidget"
|
||||
v-if="displayMilitaryZipAlert"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
ref="alertMobileOnly"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertMobileOnlyWidget"
|
||||
v-if="displayServiceableMobileOnly"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
ref="alertRecalNoMobile"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertRecalNoMobileWidget"
|
||||
v-if="displayRecalibrationWarning"
|
||||
@text-link-clicked="openModalAction"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
ref="alertInshopOnly"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertInshopOnlyWidget"
|
||||
v-if="displayServiceableInshopOnly"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
ref="alertNoShops"
|
||||
class="my-5"
|
||||
cmsWidgetName="AlertNoShopsWidget"
|
||||
v-if="displayNoShopsAlert"
|
||||
alertClass="alert-warning" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center appointment-type">
|
||||
<div class="col-md-6">
|
||||
<appointmentTypeQuestion
|
||||
v-model="selectedAppointmentType"
|
||||
v-show="isAppointmentTypeDisplayed"
|
||||
:isServiceableMobile="isServiceableMobile"
|
||||
:isServiceableInshop="isServiceableInshop"
|
||||
:isDisplayed="isAppointmentTypeDisplayed"
|
||||
ref="appointmentTypeQuestion"
|
||||
groupName="appointmentTypeQuestion"
|
||||
cmsWidgetName="AppointmentTypeQuestionWidget"
|
||||
validationRules="option-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<mobileLocationModalQuestions
|
||||
customComponentId="mobileLocationQuestions"
|
||||
v-if="selectedAppointmentType === 'Mobile'"
|
||||
v-model="mobileLocationQuestions"
|
||||
:mobileFeePart="mobileFeePart"
|
||||
@updated-mobile-fee-part="setMobileFeePart"
|
||||
@updated-serviceability="setServiceabilityDetails"
|
||||
@updated-contains-military-base="setContainsMilitaryBase"
|
||||
validationRules="mobile-location-required"
|
||||
ref="mobileLocationQuestions"
|
||||
linkWidgetName="MobileLocationLinkWidget"
|
||||
modalWidgetName="MobileLocationModalWidget"
|
||||
:onZipUpdateCallback="reloadShopData" />
|
||||
|
||||
<shopQuestion
|
||||
ref="shopQuestion"
|
||||
v-show="isShopQuestionDisplayed"
|
||||
v-model="selectedProvider"
|
||||
:selectedAppointmentType="selectedAppointmentType"
|
||||
:isDisplayed="isShopQuestionDisplayed"
|
||||
@updated-shop-list="setUpdatedShopList"
|
||||
cmsWidgetName="ShopQuestionWidget" />
|
||||
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid || displayNoShopsAlert"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
|
@ -96,7 +121,7 @@ import appointmentTypeQuestion from "@/layouts/service-location/appointment-type
|
|||
import shopQuestion from "@/layouts/service-location/shop-question/shop-question";
|
||||
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import { Form } from "vee-validate";
|
||||
|
|
@ -118,6 +143,8 @@ import store from "@/store";
|
|||
import { defineRule } from "vee-validate";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
|
||||
const MOBILE_FEE_PART_TYPE = "MOBILE FEE";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("mobile-location-required", (value) => {
|
||||
if (
|
||||
|
|
@ -159,11 +186,15 @@ export default {
|
|||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
|
||||
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
|
||||
const getZipCodeData = baseMixin.methods.getZipCodeData(serviceZipCode);
|
||||
const getZipCodeData = baseMixin.methods.getZipCodeData(serviceZipCode, "service-location");
|
||||
|
||||
const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
|
||||
const serviceabilityDetailsPromise = getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
|
||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, "service-location");
|
||||
|
||||
const shopQuestionInitialDataPromise = shopQuestion.methods.loadInitialData(serviceZipCode);
|
||||
|
||||
|
|
@ -395,6 +426,43 @@ export default {
|
|||
backButtonAction() {
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
updateAndSaveSupportingItems() {
|
||||
const supportingItems = store.getters.lineItems.supportingItems;
|
||||
// if we have a mobile fee, then save/update supporting items
|
||||
if (this.selectedAppointmentType == "Mobile") {
|
||||
const mobileFeeIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == MOBILE_FEE_PART_TYPE
|
||||
);
|
||||
// If it already exists, update the price with latest data
|
||||
if (mobileFeeIndex >= 0) {
|
||||
supportingItems[mobileFeeIndex].laborAmount = this.mobileFeePart.laborAmount;
|
||||
supportingItems[mobileFeeIndex].selingPrice = this.mobileFeePart.selingPrice;
|
||||
supportingItems[mobileFeeIndex].kitPrice = this.mobileFeePart.kitPrice;
|
||||
} else {
|
||||
supportingItems.push(this.mobileFeePart);
|
||||
}
|
||||
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
|
||||
supportingItems,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
// if it's not a mobile, then make sure we remove any that may have been added
|
||||
const removeMobileFeeIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == MOBILE_FEE_PART_TYPE
|
||||
);
|
||||
|
||||
if (removeMobileFeeIndex >= 0) {
|
||||
supportingItems.splice(removeMobileFeeIndex, 1);
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
|
||||
supportingItems,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
await this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SERVICE_LOCATION,
|
||||
|
|
@ -421,6 +489,7 @@ export default {
|
|||
false
|
||||
);
|
||||
|
||||
this.updateAndSaveSupportingItems();
|
||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||
},
|
||||
},
|
||||
|
|
@ -450,7 +519,7 @@ export default {
|
|||
appointmentTypeQuestion,
|
||||
mobileLocationModalQuestions,
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
|
|
@ -461,6 +530,20 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.appointment-type {
|
||||
@include media-breakpoint-up(md) {
|
||||
.col {
|
||||
flex: 0 0 auto;
|
||||
width: 49.33333333%;
|
||||
}
|
||||
}
|
||||
@include media-breakpoint-up(xl) {
|
||||
.col {
|
||||
flex: 0 0 auto;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.question-text {
|
||||
& > span {
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -133,7 +133,10 @@ export default {
|
|||
if (this.internalModel.zipCode !== this.modelValue.zipCode) {
|
||||
this.resetAlerts();
|
||||
|
||||
const zipCodeData = await this.getZipCodeData(this.internalModel.zipCode);
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.zipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
if (!zipCodeData.isValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
|
|
@ -144,10 +147,17 @@ export default {
|
|||
|
||||
// retrieve mobile fee part
|
||||
const serviceZipCode = this.internalModel.zipCode;
|
||||
const mobileFeePart = await getPricedMobileFeePart(serviceZipCode);
|
||||
const mobileFeePart = await getPricedMobileFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode);
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
);
|
||||
|
||||
// update content related to service zip code
|
||||
this.$emit("updated-mobile-fee-part", mobileFeePart);
|
||||
|
|
|
|||
|
|
@ -311,8 +311,7 @@ describe("shop-question.vue", () => {
|
|||
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||
|
||||
// Assert
|
||||
expect(showMoreShopsLink.exists()).toBe(true);
|
||||
expect(showMoreShopsLink.isVisible()).toBe(false);
|
||||
expect(showMoreShopsLink.exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("Should display the next three shops when the 'Show more location' link is clicked", async () => {
|
||||
|
|
@ -375,6 +374,12 @@ describe("shop-question.vue", () => {
|
|||
await wrapper.vm.$nextTick();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.setData({
|
||||
displaySeeMoreLocationsLink: true,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const showMoreShopsLink = wrapper.findComponent({ ref: "showMoreShopsLink" });
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
validationRules="option-required"
|
||||
:additionalButtonData="additionalButtonData" />
|
||||
<textLink
|
||||
v-show="displaySeeMoreLocationsLink"
|
||||
v-if="displaySeeMoreLocationsLink"
|
||||
ref="showMoreShopsLink"
|
||||
class="show-more-shops-link"
|
||||
id="showMoreShopsId"
|
||||
|
|
@ -127,9 +127,13 @@ export default {
|
|||
return this.loadData(serviceZipCode);
|
||||
},
|
||||
loadData(serviceZipCode) {
|
||||
return baseMixin.methods.dispatchStoreAction(storeActions.GET_PROVIDERS, {
|
||||
serviceZipCode: serviceZipCode,
|
||||
});
|
||||
return baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_PROVIDERS,
|
||||
{
|
||||
serviceZipCode: serviceZipCode,
|
||||
},
|
||||
"service-location"
|
||||
);
|
||||
},
|
||||
initializeComponent(shopQuestionInitialData) {
|
||||
this.shopProviders = shopQuestionInitialData.shopProviders;
|
||||
|
|
|
|||
|
|
@ -173,9 +173,10 @@ describe("vehicle-damage.vue", () => {
|
|||
//Assert
|
||||
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
|
||||
expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace);
|
||||
expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith(
|
||||
expect(baseMixin.methods.dispatchStoreActionWithLogging).toBeCalledWith(
|
||||
storeActions.GET_DAMAGE_OPTIONS,
|
||||
{ carId: "C00000000" }
|
||||
{ carId: "C00000000" },
|
||||
"vehicle-damage"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -268,9 +269,10 @@ describe("vehicle-damage.vue", () => {
|
|||
//Assert
|
||||
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
|
||||
expect(wrapper.vm.selectedGlassToReplace()).toEqual(expectedGlassToReplace);
|
||||
expect(baseMixin.methods.dispatchStoreAction).toBeCalledWith(
|
||||
expect(baseMixin.methods.dispatchStoreActionWithLogging).toBeCalledWith(
|
||||
storeActions.GET_DAMAGE_OPTIONS,
|
||||
{ carId: "C00000000" }
|
||||
{ carId: "C00000000" },
|
||||
"vehicle-damage"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -789,6 +791,7 @@ function setupMocks({ pageHeaderWidgetHeaderText, mountOptionsMockData, funnelCo
|
|||
mountOptionsMockData = Object.assign(mountOptionsMockDataDefault, mountOptionsMockData);
|
||||
//Mock api responses
|
||||
baseMixin.methods.dispatchStoreAction = jest.fn();
|
||||
baseMixin.methods.dispatchStoreActionWithLogging = jest.fn();
|
||||
const apiResponses = {
|
||||
cmsContent: {
|
||||
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
|
||||
|
|
|
|||
|
|
@ -1,56 +1,77 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<alert
|
||||
ref="vehicleChangeAlert"
|
||||
v-if="shouldDisplayVehicleChangeAlert"
|
||||
class="mt-5 mb-0"
|
||||
cmsWidgetName="VehicleChangeAlert"
|
||||
alertClass="alert-warning"
|
||||
:isDismissible="false" />
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
cmsWidgetName="DamageLocationQuestion"
|
||||
v-model="selectedDamageLocations"
|
||||
groupName="DamageLocationQuestion" />
|
||||
<windshieldOptions
|
||||
ref="windshieldOptions"
|
||||
v-model="selectedWindshieldOptions"
|
||||
:hasRepairReplaceConflict="hasRepairReplaceConflict"
|
||||
:hasSplitSingleConflict="hasSplitSingleConflict"
|
||||
:selectedDamageLocations="selectedDamageLocations" />
|
||||
<alert
|
||||
v-if="hasRepairReplaceConflict"
|
||||
class="my-5"
|
||||
cmsWidgetName="HasReplacementConflict"
|
||||
alertClass="alert-danger"
|
||||
:isDismissible="false" />
|
||||
<sideDoorOptions
|
||||
ref="sideDoorOptions"
|
||||
cmsWidgetName="SideDoorSideQuestion"
|
||||
groupName="SideDoorSideQuestion"
|
||||
v-model="sideDoorOptionsData"
|
||||
v-show="!hasRepairReplaceConflict"
|
||||
:selectedDamageLocations="selectedDamageLocations" />
|
||||
<replaceOptionsQuestion
|
||||
ref="backGlassOptions"
|
||||
cmsWidgetName="RearReplaceOptionsQuestion"
|
||||
:isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict"
|
||||
v-model="selectedRearReplaceOptions"
|
||||
groupName="BackGlassReplaceOptionsQuestion"
|
||||
validationRules="replace-options-required" />
|
||||
<funnel-footer
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
<div class="container-fluid page-container-grouped-styles vehicle-damage">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<alert
|
||||
ref="vehicleChangeAlert"
|
||||
v-if="shouldDisplayVehicleChangeAlert"
|
||||
class="mt-5 mb-0"
|
||||
cmsWidgetName="VehicleChangeAlert"
|
||||
alertClass="alert-warning"
|
||||
:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-8 col-xl-6 col-xxl-4">
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
cmsWidgetName="DamageLocationQuestion"
|
||||
v-model="selectedDamageLocations"
|
||||
groupName="DamageLocationQuestion" />
|
||||
|
||||
<windshieldOptions
|
||||
class="windshield-options"
|
||||
ref="windshieldOptions"
|
||||
v-model="selectedWindshieldOptions"
|
||||
:hasRepairReplaceConflict="hasRepairReplaceConflict"
|
||||
:hasSplitSingleConflict="hasSplitSingleConflict"
|
||||
:selectedDamageLocations="selectedDamageLocations" />
|
||||
|
||||
<alert
|
||||
v-if="hasRepairReplaceConflict"
|
||||
class="my-5"
|
||||
cmsWidgetName="HasReplacementConflict"
|
||||
alertClass="alert-danger"
|
||||
:isDismissible="false" />
|
||||
|
||||
<sideDoorOptions
|
||||
ref="sideDoorOptions"
|
||||
cmsWidgetName="SideDoorSideQuestion"
|
||||
groupName="SideDoorSideQuestion"
|
||||
v-model="sideDoorOptionsData"
|
||||
v-show="!hasRepairReplaceConflict"
|
||||
:selectedDamageLocations="selectedDamageLocations" />
|
||||
|
||||
<replaceOptionsQuestion
|
||||
ref="backGlassOptions"
|
||||
cmsWidgetName="RearReplaceOptionsQuestion"
|
||||
:isAvailable="isRearWindowDamageLocation && !hasRepairReplaceConflict"
|
||||
v-model="selectedRearReplaceOptions"
|
||||
groupName="BackGlassReplaceOptionsQuestion"
|
||||
validationRules="replace-options-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -59,7 +80,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options";
|
||||
|
|
@ -90,9 +111,10 @@ export default {
|
|||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
|
||||
const damageOptionsPromise = baseMixin.methods.dispatchStoreAction(
|
||||
const damageOptionsPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_DAMAGE_OPTIONS,
|
||||
{ carId: store.getters.vehicle.carId }
|
||||
{ carId: store.getters.vehicle.carId },
|
||||
"vehicle-damage"
|
||||
);
|
||||
|
||||
// Settle promises and get results
|
||||
|
|
@ -325,8 +347,10 @@ export default {
|
|||
);
|
||||
|
||||
if (this.isWindshieldRepair) {
|
||||
const supportingItems = await this.dispatchStoreAction(
|
||||
storeActions.GET_SUPPORTING_ITEMS
|
||||
const supportingItems = await this.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_SUPPORTING_ITEMS,
|
||||
null,
|
||||
"vehicle-damage"
|
||||
);
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SUPPORTING_ITEMS,
|
||||
|
|
@ -509,7 +533,7 @@ export default {
|
|||
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
sideDoorOptions,
|
||||
|
|
@ -521,3 +545,16 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vehicle-damage {
|
||||
.windshield-options {
|
||||
@include media-breakpoint-up(md) {
|
||||
.col {
|
||||
flex: 0 0 auto;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {}
|
|||
partQuestionRearWrapper.vm.initializeComponent = glassPartQuestion.methods.initializeComponent;
|
||||
|
||||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
|
||||
// wrapper.vm.$refs.onSubmit = jest.fn();
|
||||
// wrapper.vm.$refs.onInvalidSubmit = jest.fn();
|
||||
|
|
|
|||
|
|
@ -1,42 +1,58 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<div class="page-container-grouped-styles vehicle-parts small-question-text">
|
||||
<div class="vehicle-parts small-question-text">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
ref="vehicleBanner"
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<div class="prevent-squish my-5">
|
||||
<div class="row">
|
||||
<div class="col pb-0">
|
||||
<alert
|
||||
class="rounded border-0 shadow-sm"
|
||||
alertClass="alert-warning"
|
||||
cmsWidgetName="AlertWidget"
|
||||
:isDismissible="false" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<vehicleBanner
|
||||
ref="vehicleBanner"
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader
|
||||
ref="funnelSubHeader"
|
||||
cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="prevent-squish my-5">
|
||||
<div class="row">
|
||||
<div class="col pb-0">
|
||||
<alert
|
||||
class="rounded border-0 shadow-sm"
|
||||
alertClass="alert-warning"
|
||||
cmsWidgetName="AlertWidget"
|
||||
:isDismissible="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(item, i) in PartsOrQuestions" :key="i">
|
||||
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->
|
||||
<hr v-if="i > 0" />
|
||||
<glassPartQuestion
|
||||
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
|
||||
v-model="selectedGlassParts[item.glassLocation + '-' + item.glassName]"
|
||||
:glassLocation="item.glassLocation"
|
||||
:glassName="item.glassName"
|
||||
:colorAnswers="item.colorAnswers"
|
||||
:alreadyPopulatedPartsData="alreadyPopulatedPartsData" />
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<div v-for="(item, i) in PartsOrQuestions" :key="i">
|
||||
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->
|
||||
<hr v-if="i > 0" />
|
||||
<glassPartQuestion
|
||||
:ref="`${RefPrefix}-${item.glassLocation}-${item.glassName}`"
|
||||
v-model="
|
||||
selectedGlassParts[item.glassLocation + '-' + item.glassName]
|
||||
"
|
||||
:glassLocation="item.glassLocation"
|
||||
:glassName="item.glassName"
|
||||
:colorAnswers="item.colorAnswers"
|
||||
:alreadyPopulatedPartsData="alreadyPopulatedPartsData" />
|
||||
</div>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="isForwardActionDisabled"
|
||||
@back-clicked="navigateBack"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="isForwardActionDisabled"
|
||||
@back-clicked="navigateBack"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -48,7 +64,7 @@ import glassPartQuestion from "@/layouts/vehicle-parts/glass-part-question/glass
|
|||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
// Supporting Files
|
||||
|
|
@ -178,7 +194,7 @@ export default {
|
|||
}
|
||||
// If no parts could be matched, throw an error (isForwardActionDisabled is based off of matchedParts)
|
||||
if (this.isForwardActionDisabled) {
|
||||
this.$refs.funnelFooter.removeLoader();
|
||||
this.$refs.navbar.removeLoader();
|
||||
throw new Error("Could not match any parts to the selected parts");
|
||||
}
|
||||
|
||||
|
|
@ -221,9 +237,19 @@ export default {
|
|||
funnelHeader,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
alert,
|
||||
loadingModal,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vehicle-parts {
|
||||
label {
|
||||
&.list-card {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<dropdownQuestion
|
||||
disableAutoFill
|
||||
class="mb-5 mt-4"
|
||||
v-model="selectedValue"
|
||||
:valueAsKey="true"
|
||||
:disableValidationIfElementDisabled="true" />
|
||||
|
|
|
|||
|
|
@ -1,77 +1,67 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles position-relative">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<div class="select-car">
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="select-car-form rounded">
|
||||
<funnelSubHeader
|
||||
cmsWidgetName="FunnelSubHeaderWidget"
|
||||
class="siteSubHeader" />
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="siteSubHeader" />
|
||||
<vehicleQuestion
|
||||
ref="vehicleYearQuestion"
|
||||
v-model="selectedYear"
|
||||
:isDisabled="!yearOptions.length"
|
||||
:options="yearOptions"
|
||||
cmsWidgetName="VehicleYearQuestion"
|
||||
validationRules="year-required"
|
||||
placeHolderText="Select year"
|
||||
inputId="yearQuestionField" />
|
||||
|
||||
<vehicleQuestion
|
||||
ref="vehicleYearQuestion"
|
||||
class="mb-2 mt-4"
|
||||
v-model="selectedYear"
|
||||
:isDisabled="!yearOptions.length"
|
||||
:options="yearOptions"
|
||||
cmsWidgetName="VehicleYearQuestion"
|
||||
validationRules="year-required"
|
||||
placeHolderText="Select year"
|
||||
inputId="yearQuestionField" />
|
||||
<vehicleQuestion
|
||||
ref="vehicleMakeQuestion"
|
||||
v-model="selectedMake"
|
||||
:isDisabled="!makeOptions.length"
|
||||
:options="makeOptions"
|
||||
cmsWidgetName="VehicleMakeQuestion"
|
||||
validationRules="make-required"
|
||||
placeHolderText="Select make"
|
||||
inputId="makeQuestionField" />
|
||||
|
||||
<vehicleQuestion
|
||||
ref="vehicleMakeQuestion"
|
||||
class="mb-2 mt-4"
|
||||
v-model="selectedMake"
|
||||
:isDisabled="!makeOptions.length"
|
||||
:options="makeOptions"
|
||||
cmsWidgetName="VehicleMakeQuestion"
|
||||
validationRules="make-required"
|
||||
placeHolderText="Select make"
|
||||
inputId="makeQuestionField" />
|
||||
<vehicleQuestion
|
||||
ref="vehicleModelQuestion"
|
||||
v-model="selectedModel"
|
||||
cmsWidgetName="VehicleModelQuestion"
|
||||
:isDisabled="!modelOptions.length"
|
||||
:options="modelOptions"
|
||||
validationRules="model-required"
|
||||
placeHolderText="Select model"
|
||||
inputId="modelQuestionField" />
|
||||
|
||||
<vehicleQuestion
|
||||
ref="vehicleModelQuestion"
|
||||
class="mb-2 mt-4"
|
||||
v-model="selectedModel"
|
||||
cmsWidgetName="VehicleModelQuestion"
|
||||
:isDisabled="!modelOptions.length"
|
||||
:options="modelOptions"
|
||||
validationRules="model-required"
|
||||
placeHolderText="Select model"
|
||||
inputId="modelQuestionField" />
|
||||
<vehicleQuestion
|
||||
ref="vehicleStyleQuestion"
|
||||
v-model="selectedStyle"
|
||||
cmsWidgetName="VehicleStyleQuestion"
|
||||
:isDisabled="!styleOptions.length"
|
||||
:options="styleOptions"
|
||||
validationRules="style-required"
|
||||
placeHolderText="Select style"
|
||||
inputId="styleQuestionField" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="displayGeneric"
|
||||
:displayVehicleImage="imageUrl"
|
||||
:vehicleCategory="category"
|
||||
:displayUnmatchedVehicleIcon="unmatchedVehicleIcon"
|
||||
class="mt-5 vehicle-footer"
|
||||
ref="banner" />
|
||||
|
||||
<vehicleQuestion
|
||||
ref="vehicleStyleQuestion"
|
||||
class="mb-2 mt-4"
|
||||
v-model="selectedStyle"
|
||||
cmsWidgetName="VehicleStyleQuestion"
|
||||
:isDisabled="!styleOptions.length"
|
||||
:options="styleOptions"
|
||||
validationRules="style-required"
|
||||
placeHolderText="Select style"
|
||||
inputId="styleQuestionField" />
|
||||
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="displayGeneric"
|
||||
:displayVehicleImage="imageUrl"
|
||||
:vehicleCategory="category"
|
||||
:displayUnmatchedVehicleIcon="unmatchedVehicleIcon"
|
||||
class="mt-5 mb-3"
|
||||
ref="banner" />
|
||||
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
ref="funnelFooter" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
ref="navbar" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -81,7 +71,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import vehicleQuestion from "@/layouts/vehicle/vehicle-question/vehicle-question";
|
||||
|
|
@ -131,9 +121,10 @@ export default {
|
|||
const experimentForLogging = store.getters.applicationUser.experiments.find(
|
||||
(e) => e.universeName === experimentUniverses.CONCEPT_FUNNEL
|
||||
);
|
||||
const yearQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
|
||||
const yearQuestionInitialDataPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_YEARS,
|
||||
{}
|
||||
{},
|
||||
"vehicle"
|
||||
);
|
||||
var makeQuestionInitialDataPromise = null;
|
||||
var modelQuestionInitialDataPromise = null;
|
||||
|
|
@ -144,35 +135,38 @@ export default {
|
|||
store.getters.order.vehicle.model &&
|
||||
store.getters.order.vehicle.style
|
||||
) {
|
||||
makeQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
|
||||
makeQuestionInitialDataPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_MAKES,
|
||||
{
|
||||
year: store.getters.order.vehicle.year,
|
||||
}
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
|
||||
modelQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
|
||||
modelQuestionInitialDataPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_MODELS,
|
||||
{
|
||||
year: store.getters.order.vehicle.year,
|
||||
make: store.getters.order.vehicle.make,
|
||||
}
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
|
||||
styleQuestionInitialDataPromise = baseMixin.methods.dispatchStoreAction(
|
||||
styleQuestionInitialDataPromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_STYLES,
|
||||
{
|
||||
year: store.getters.order.vehicle.year,
|
||||
make: store.getters.order.vehicle.make,
|
||||
model: store.getters.order.vehicle.model,
|
||||
}
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
}
|
||||
|
||||
// If the concept funnel experiment is found, as it should be when coming from safelite.com, then log the experiment exposure.
|
||||
if (experimentForLogging !== undefined) {
|
||||
// Log experiment exposure
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.LOG_EXPERIMENT_EXPOSURE,
|
||||
{
|
||||
userId: getDeviceIdValue(),
|
||||
|
|
@ -180,6 +174,7 @@ export default {
|
|||
pageName: to.query.fmgPage,
|
||||
experiment: experimentForLogging,
|
||||
},
|
||||
"vehicle",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
|
@ -268,8 +263,13 @@ export default {
|
|||
model
|
||||
);
|
||||
this.styleOptions = result?.data;
|
||||
if (this.styleOptions.length === 1) this.selectedStyle = this.styleOptions[0];
|
||||
else this.selectedStyle = null;
|
||||
if (this.styleOptions.length === 1) {
|
||||
const sameStyle = this.selectedStyle === this.styleOptions[0];
|
||||
this.selectedStyle = this.styleOptions[0];
|
||||
if (sameStyle) {
|
||||
this.getVehicleDetails();
|
||||
}
|
||||
} else this.selectedStyle = null;
|
||||
} else {
|
||||
this.styleOptions = [];
|
||||
this.selectedStyle = null;
|
||||
|
|
@ -277,18 +277,7 @@ export default {
|
|||
},
|
||||
async selectedStyle(style) {
|
||||
if (style) {
|
||||
this.carId = null;
|
||||
const result = await this.getVehicle(
|
||||
this.selectedYear,
|
||||
this.selectedMake,
|
||||
this.selectedModel,
|
||||
style
|
||||
);
|
||||
this.carId = result?.data.carId;
|
||||
this.category = result?.data.category;
|
||||
this.imageUrl = result?.data.imageUrl;
|
||||
this.imageVifNumber = result?.data.imageVifNumber;
|
||||
this.imageVifColor = result?.data.imageVifColor;
|
||||
this.getVehicleDetails();
|
||||
} else {
|
||||
this.imageUrl = null;
|
||||
}
|
||||
|
|
@ -308,16 +297,34 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getVehicle(year, make, model, style) {
|
||||
return this.dispatchStoreAction(this.storeActions.GET_VEHICLE, {
|
||||
year: year,
|
||||
make: make,
|
||||
model: model,
|
||||
style: style,
|
||||
});
|
||||
return this.dispatchStoreActionWithLogging(
|
||||
this.storeActions.GET_VEHICLE,
|
||||
{
|
||||
year: year,
|
||||
make: make,
|
||||
model: model,
|
||||
style: style,
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
},
|
||||
arePagePrerequisitesValid() {
|
||||
return true;
|
||||
},
|
||||
async getVehicleDetails() {
|
||||
this.carId = null;
|
||||
const result = await this.getVehicle(
|
||||
this.selectedYear,
|
||||
this.selectedMake,
|
||||
this.selectedModel,
|
||||
this.selectedStyle
|
||||
);
|
||||
this.carId = result?.data.carId;
|
||||
this.category = result?.data.category;
|
||||
this.imageUrl = result?.data.imageUrl;
|
||||
this.imageVifNumber = result?.data.imageVifNumber;
|
||||
this.imageVifColor = result?.data.imageVifColor;
|
||||
},
|
||||
|
||||
async forwardButtonAction() {
|
||||
this.dispatchStoreAction(
|
||||
|
|
@ -358,24 +365,36 @@ export default {
|
|||
return store.getters.vehicle.style;
|
||||
},
|
||||
async getMakeOptions(year) {
|
||||
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MAKES, {
|
||||
year: year,
|
||||
});
|
||||
return await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_MAKES,
|
||||
{
|
||||
year: year,
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
},
|
||||
|
||||
async getModelOptions(year, make) {
|
||||
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_MODELS, {
|
||||
year: year,
|
||||
make: make,
|
||||
});
|
||||
return await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_MODELS,
|
||||
{
|
||||
year: year,
|
||||
make: make,
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
},
|
||||
|
||||
async getStyleOptions(year, make, model) {
|
||||
return await baseMixin.methods.dispatchStoreAction(storeActions.GET_VEHICLE_STYLES, {
|
||||
year: year,
|
||||
make: make,
|
||||
model: model,
|
||||
});
|
||||
return await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_VEHICLE_STYLES,
|
||||
{
|
||||
year: year,
|
||||
make: make,
|
||||
model: model,
|
||||
},
|
||||
"vehicle"
|
||||
);
|
||||
},
|
||||
getImagefromStore() {
|
||||
return store.getters.vehicle.imageUrl;
|
||||
|
|
@ -396,7 +415,7 @@ export default {
|
|||
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
funnelSubHeader,
|
||||
vehicleBanner,
|
||||
Form,
|
||||
|
|
@ -406,12 +425,11 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.select-car-form {
|
||||
margin-left: 0.75rem;
|
||||
margin-right: 0.75rem;
|
||||
.vehicle-footer {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.siteSubHeader {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -47,14 +47,14 @@ jest.mock("@/helpers/damage-helper", () => ({
|
|||
}));
|
||||
|
||||
describe("vin-lookup.vue", () => {
|
||||
it("Should update the funnel-footer forward button when VIN is changed", (done) => {
|
||||
it("Should update the nav-bar forward button when VIN is changed", (done) => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
//Act
|
||||
wrapper.setData({ vin: "newValue" });
|
||||
//Assert
|
||||
wrapper.vm.$nextTick(() => {
|
||||
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toBeCalled();
|
||||
expect(wrapper.vm.$refs.navbar.updateButtonText).toBeCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
@ -368,8 +368,8 @@ function mockOutPromises({ carId, isZipValid = true, isZipServiceable = true })
|
|||
}
|
||||
|
||||
function mockOutStubFunctions(wrapper) {
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.navbar.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.navbar.removeLoader = jest.fn();
|
||||
wrapper.vm.getZipCodeData = jest
|
||||
.fn()
|
||||
.mockReturnValue({ isValid: true, isServiceable: true, state: "OH" });
|
||||
|
|
|
|||
|
|
@ -1,131 +1,127 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="page-container-grouped-styles">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="VinNumberQuestionWidget"
|
||||
v-model="vin"
|
||||
customInputId="vin"
|
||||
isRequired
|
||||
:validationRules="
|
||||
!vinPopulatedOnPageLoad ? 'vin-required|vin-format' : ''
|
||||
"
|
||||
:isDisabled="vinPopulatedOnPageLoad"
|
||||
maxLength="17"
|
||||
:mask="vinMask"
|
||||
includeImageQuestion
|
||||
:imageQuestionSubmitHandler="getVinFromImage"
|
||||
:maxFileSize="imageUploadMaxFileSize"
|
||||
@image-lookup-error="displayVinScanAlert"
|
||||
@image-validity-error="displayVinScanAlert"
|
||||
data-test="vin-lookup-field"
|
||||
ref="vinLookupQuestion" />
|
||||
</div>
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<alert
|
||||
cmsWidgetName="AlertVinScanFailed"
|
||||
v-if="displayVinScanFailedAlert"
|
||||
alertClass="alert-danger" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4 mt-5"
|
||||
cmsWidgetName="VinNumberQuestionWidget"
|
||||
v-model="vin"
|
||||
customInputId="vin"
|
||||
isRequired
|
||||
:validationRules="!vinPopulatedOnPageLoad ? 'vin-required|vin-format' : ''"
|
||||
:isDisabled="vinPopulatedOnPageLoad"
|
||||
maxLength="17"
|
||||
:mask="vinMask"
|
||||
includeImageQuestion
|
||||
:imageQuestionSubmitHandler="getVinFromImage"
|
||||
:maxFileSize="imageUploadMaxFileSize"
|
||||
@image-lookup-error="displayVinScanAlert"
|
||||
@image-validity-error="displayVinScanAlert"
|
||||
data-test="vin-lookup-field"
|
||||
ref="vinLookupQuestion" />
|
||||
|
||||
<alert
|
||||
cmsWidgetName="AlertVinScanFailed"
|
||||
v-if="displayVinScanFailedAlert"
|
||||
alertClass="alert-danger" />
|
||||
|
||||
<vinInformation class="mb-4" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
customInputId="serviceZipCode"
|
||||
mask="#####"
|
||||
isRequired
|
||||
validationRules="zip-required|zip-format" />
|
||||
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
customInputId="emailAddress"
|
||||
isRequired
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertPerfectMatchInsuranceVerifiedHeader"
|
||||
:manualCopy="AlertPerfectMatchInsuranceVerifiedBody"
|
||||
v-model="customAlertData"
|
||||
v-if="
|
||||
vinPopulatedOnPageLoad &&
|
||||
isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
alertClass="alert-warning" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
alertClass="alert-danger" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
v-model="customAlertData"
|
||||
v-if="displayVinNotFoundAlert"
|
||||
alertClass="alert-danger"
|
||||
cmsWidgetName="AlertVinNotFoundWidget" />
|
||||
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertPerfectMatchInsuranceNotVerifiedHeader"
|
||||
:manualCopy="AlertPerfectMatchInsuranceNotVerifiedBody"
|
||||
v-model="customAlertData"
|
||||
v-if="
|
||||
vinPopulatedOnPageLoad &&
|
||||
!isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<vinInformation />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-0 mt-4">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
customInputId="serviceZipCode"
|
||||
mask="#####"
|
||||
isRequired
|
||||
validationRules="zip-required|zip-format" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col">
|
||||
<textboxQuestion
|
||||
cmsWidgetName="EmailAddressQuestionWidget"
|
||||
v-model="emailAddress"
|
||||
customInputId="emailAddress"
|
||||
isRequired
|
||||
validationRules="email-address-required|email-address-format" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-0">
|
||||
<div class="col">
|
||||
<textBlock cmsWidgetName="QuoteEmailTextBlockWidget" typeStyle="caption" />
|
||||
</div>
|
||||
</div>
|
||||
<alert
|
||||
ref="alertInvalidZip"
|
||||
v-if="displayInvalidZipAlert"
|
||||
class="my-4"
|
||||
cmsWidgetName="AlertInvalidZipWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertPerfectMatchInsuranceVerifiedHeader"
|
||||
:manualCopy="AlertPerfectMatchInsuranceVerifiedBody"
|
||||
v-model="customAlertData"
|
||||
v-if="
|
||||
vinPopulatedOnPageLoad &&
|
||||
isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertMatchedDifferentVehicleHeader"
|
||||
:manualCopy="AlertMatchedDifferentVehicleBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayMatchedDifferentVehicleAlert"
|
||||
alertClass="alert-warning" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertNonServiceableZipHeader"
|
||||
:manualCopy="AlertNonServiceableZipBody"
|
||||
v-model="customAlertData"
|
||||
v-if="displayNonServiceableZipAlert"
|
||||
alertClass="alert-danger" />
|
||||
<alert
|
||||
class="my-4"
|
||||
v-model="customAlertData"
|
||||
v-if="displayVinNotFoundAlert"
|
||||
alertClass="alert-danger"
|
||||
cmsWidgetName="AlertVinNotFoundWidget" />
|
||||
<alert
|
||||
class="my-4"
|
||||
:manualHeadline="AlertPerfectMatchInsuranceNotVerifiedHeader"
|
||||
:manualCopy="AlertPerfectMatchInsuranceNotVerifiedBody"
|
||||
v-model="customAlertData"
|
||||
v-if="
|
||||
vinPopulatedOnPageLoad &&
|
||||
!isInsuranceVerified &&
|
||||
!displayInvalidZipAlert &&
|
||||
!displayNonServiceableZipAlert
|
||||
"
|
||||
alertClass="alert-success" />
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="funnelFooter"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -134,7 +130,7 @@
|
|||
<script>
|
||||
// Components
|
||||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import vehicleBanner from "@/fmg-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
|
|
@ -255,9 +251,10 @@ export default {
|
|||
|
||||
// If this is a new VIN Lookup, do both a Vehicle Lookup and a Zip Validation
|
||||
if (!this.vinPopulatedOnPageLoad) {
|
||||
const vehicleLookupResponse = this.dispatchStoreAction(
|
||||
const vehicleLookupResponse = this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VEHICLE_BY_VIN,
|
||||
{ vin: this.vin }
|
||||
{ vin: this.vin },
|
||||
"vin-lookup"
|
||||
);
|
||||
|
||||
// Settle promises and get results
|
||||
|
|
@ -278,7 +275,7 @@ export default {
|
|||
const isZipValid = resultMap.zipCodeData.isValid;
|
||||
if (this.serviceZipCode && !isZipValid) {
|
||||
this.displayInvalidZipAlert = true;
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
this.displayInvalidZipAlert = false;
|
||||
|
||||
|
|
@ -295,7 +292,7 @@ export default {
|
|||
}
|
||||
|
||||
// Remove loader and stop processing the page.
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// Check if the CarId is different from the lookup vs what is in state currently.
|
||||
|
|
@ -311,14 +308,15 @@ export default {
|
|||
this.displayMatchedDifferentVehicleAlert = true;
|
||||
|
||||
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(
|
||||
resultMap.vehicleLookupResponse.carId
|
||||
resultMap.vehicleLookupResponse.carId,
|
||||
"vin-lookup"
|
||||
);
|
||||
|
||||
// Update button "Continue with..."
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
`Continue with ${resultMap.vehicleLookupResponse.year} ${resultMap.vehicleLookupResponse.make} ${resultMap.vehicleLookupResponse.model}`
|
||||
);
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
// Save vin, vehicle, customer and service information
|
||||
|
|
@ -380,7 +378,7 @@ export default {
|
|||
this.displayInvalidZipAlert = true;
|
||||
}
|
||||
|
||||
return this.$refs.funnelFooter.removeLoader();
|
||||
return this.$refs.navbar.removeLoader();
|
||||
},
|
||||
async navigateForward() {
|
||||
if (this.isCarIdDifferent && !this.isSelectedGlassAvailableForVehicle) {
|
||||
|
|
@ -399,7 +397,11 @@ export default {
|
|||
},
|
||||
getVinFromImage(image) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dispatchStoreAction(storeActions.LOOKUP_VIN_BY_IMAGE, image)
|
||||
this.dispatchStoreActionWithLogging(
|
||||
storeActions.LOOKUP_VIN_BY_IMAGE,
|
||||
image,
|
||||
"vin-lookup"
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data.length > 0) {
|
||||
resolve(response.data[0]);
|
||||
|
|
@ -488,7 +490,7 @@ export default {
|
|||
vin() {
|
||||
this.displayVinNotFoundAlert = false;
|
||||
this.displayVinScanFailedAlert = false;
|
||||
this.$refs.funnelFooter.updateButtonText(
|
||||
this.$refs.navbar.updateButtonText(
|
||||
this.getCmsContent("FunnelFooterWidget", "ForwardButtonText")
|
||||
);
|
||||
},
|
||||
|
|
@ -498,7 +500,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
navbar,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
textboxQuestion,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,19 @@ export default {
|
|||
|
||||
return store.dispatch(type, payload);
|
||||
},
|
||||
dispatchStoreActionWithLogging(type, payload, pageNameToLog, encodePayload = true) {
|
||||
// Encode the payload if required
|
||||
if (encodePayload) {
|
||||
encodeUriData(payload);
|
||||
}
|
||||
|
||||
const wrappedParams = {
|
||||
payload: payload,
|
||||
pageNameToLog: pageNameToLog,
|
||||
};
|
||||
|
||||
return store.dispatch(type, wrappedParams);
|
||||
},
|
||||
savePageDataToStore(page, data) {
|
||||
store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data });
|
||||
},
|
||||
|
|
@ -50,10 +63,13 @@ export default {
|
|||
const footerInfoBox = document.querySelector(".footer#infoBox");
|
||||
return footerInfoBox ? footerInfoBox.offsetHeight : 0;
|
||||
},
|
||||
async getZipCodeData(zipCode) {
|
||||
const serviceZipValidationResponse = await this.dispatchStoreAction(
|
||||
async getZipCodeData(zipCode, pageNameToLog = null) {
|
||||
const pageName = pageNameToLog ?? this.$options?.name;
|
||||
|
||||
const serviceZipValidationResponse = await this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{ zip: zipCode }
|
||||
{ zip: zipCode },
|
||||
pageName
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -101,8 +101,8 @@ describe("baseMixin.js", () => {
|
|||
|
||||
test("getZipCodeData calls dispatch", () => {
|
||||
const mixIn = getMixInInstance({});
|
||||
mixIn.methods.dispatchStoreAction = jest.fn();
|
||||
mixIn.methods.dispatchStoreAction.mockReturnValue({
|
||||
mixIn.methods.dispatchStoreActionWithLogging = jest.fn();
|
||||
mixIn.methods.dispatchStoreActionWithLogging.mockReturnValue({
|
||||
data: { isValid: true, isServiceable: true, state: "OH" },
|
||||
});
|
||||
const type = "";
|
||||
|
|
@ -118,7 +118,7 @@ describe("baseMixin.js", () => {
|
|||
|
||||
mixIn.methods.getZipCodeData(type, payload);
|
||||
|
||||
expect(mixIn.methods.dispatchStoreAction).toBeCalled();
|
||||
expect(mixIn.methods.dispatchStoreActionWithLogging).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -408,12 +408,13 @@ export default {
|
|||
for (let partOrQuestion of partsOrQuestions) {
|
||||
if (this.hasCapabilityQuestions([partOrQuestion])) {
|
||||
let capabilityQuestionsForGlassLocation = (
|
||||
await baseMixin.methods.dispatchStoreAction(
|
||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_CAPABILITY_QUESTIONS,
|
||||
{
|
||||
carId: store.getters.vehicle.carId,
|
||||
partNumber: partOrQuestion.parts[0].partNumber,
|
||||
}
|
||||
},
|
||||
currentPage
|
||||
)
|
||||
).data;
|
||||
|
||||
|
|
@ -447,9 +448,17 @@ export default {
|
|||
const payment = store.getters.payment;
|
||||
|
||||
if (store.getters.order.referralNumber?.length === 6) {
|
||||
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
|
||||
navigateToHeritageFunnel({
|
||||
shouldSaveSession: true,
|
||||
pageNameToLog: currentPage,
|
||||
loadingModal: self.$refs.loadingModal,
|
||||
});
|
||||
} else if (payment.isInsurance && payment.insuranceCoverage.isVerified) {
|
||||
navigateToHeritageFunnel({ loadingModal: self.$refs.loadingModal });
|
||||
navigateToHeritageFunnel({
|
||||
shouldSaveSession: true,
|
||||
pageNameToLog: currentPage,
|
||||
loadingModal: self.$refs.loadingModal,
|
||||
});
|
||||
} else {
|
||||
self.$router.navigateWithSaving(
|
||||
self.navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,16 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
|||
export default {
|
||||
methods: {
|
||||
async navigateForwardWithSingleCarMatch() {
|
||||
const pageName = this.$options?.name;
|
||||
|
||||
// If we have not already saved a session, we need to save one now before the lengthy call to getPartsOrQuestions
|
||||
if (!store.getters.applicationUser.savedSessionId) {
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: pageName });
|
||||
}
|
||||
|
||||
const result = await this.dispatchStoreAction(storeActions.GET_PARTS_OR_QUESTIONS);
|
||||
const result = await this.dispatchStoreAction(storeActions.GET_PARTS_OR_QUESTIONS, {
|
||||
pageNameToLog: pageName,
|
||||
});
|
||||
const partsOrQuestions = result.data.partsOrQuestions;
|
||||
|
||||
vehicleQuestionsMixin.methods.navigateForward(partsOrQuestions, this);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"
|
|||
import { routingTable } from "@/router/router-constants/routing-table.js";
|
||||
import { globalEvents, globalEventTypes } from "@/constants/events";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
||||
import { getDeviceIdValue } from "@/helpers/heritage-integration/cookie-helper";
|
||||
|
||||
// Heritage integration
|
||||
|
|
@ -28,14 +29,7 @@ import analyticsMixin from "@/mixins/analytics-mixin";
|
|||
import { experimentTriggers } from "../constants/experiments";
|
||||
import { applicationConfig } from "../constants/application-config";
|
||||
|
||||
import review from "@/layouts/review/review";
|
||||
import paymentMethod from "@/layouts/payment-method/payment-method";
|
||||
const routes = [
|
||||
{
|
||||
path: "/payment-method", // This is a temporary route for testing.
|
||||
name: "payment-method",
|
||||
component: paymentMethod,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: "root",
|
||||
|
|
@ -59,8 +53,8 @@ const routes = [
|
|||
await GoToFunnelStartOn404(next);
|
||||
}
|
||||
|
||||
// On entering the funnel "fresh", read cookie information, decide what to do next.
|
||||
if (from.redirectedFrom === undefined) {
|
||||
// On entering the funnel "fresh", read cookie information, decide what to do next. payment pages used to return from safelitehop so exclude here
|
||||
if (from.redirectedFrom === undefined && !to.query.fmgPage.startsWith("payment")) {
|
||||
// clear the saveSessionPromise - if it exists in the vuex store but a new instance was created
|
||||
// the saveSessionPromise will no longer point to a valid promise
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||
|
|
@ -71,20 +65,11 @@ const routes = [
|
|||
? to.query.isInsurance == "true"
|
||||
? true
|
||||
: false
|
||||
: null
|
||||
: null,
|
||||
to.query.fmgPage
|
||||
);
|
||||
|
||||
const pageToRedirectTo = await getPageToRouteExistingOrderTo(
|
||||
to,
|
||||
loadSessionResponse
|
||||
);
|
||||
|
||||
// If getPageToRouteExistingOrderTo determines that the return user needs to
|
||||
// go back to heritage funnel, send them there and stop our current navigation.
|
||||
if (pageToRedirectTo === "heritage") {
|
||||
await navigateToHeritageFunnel();
|
||||
return next(false);
|
||||
}
|
||||
const pageToRedirectTo = await getPageToRouteExistingOrderTo(to);
|
||||
|
||||
// Assign our fmgPage so it will load normally like the other pages.
|
||||
to.query.fmgPage = pageToRedirectTo;
|
||||
|
|
@ -181,7 +166,7 @@ router.afterEach(async (to, from) => {
|
|||
store.getters.applicationUser.savedSessionId ||
|
||||
store.getters.order.customer?.emailAddress
|
||||
) {
|
||||
await saveSession({});
|
||||
await saveSession({ pageNameToLog: to.query.fmgPage });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -284,14 +269,9 @@ async function navigate(
|
|||
fmgPage: destinationFmgPageValue,
|
||||
};
|
||||
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
const lowerCaseParams = new URLSearchParams();
|
||||
for (const [name, value] of urlParams) {
|
||||
lowerCaseParams.append(name.toLowerCase(), value);
|
||||
}
|
||||
const hasZip = lowerCaseParams.has(queryStrings.ZIP_CODE);
|
||||
const zip = lowerCaseParams.get(queryStrings.ZIP_CODE);
|
||||
const hasZip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
||||
const zip = getQuerystringParameter(queryStrings.ZIP_CODE);
|
||||
const promo = getQuerystringParameter(queryStrings.PROMO);
|
||||
|
||||
if (
|
||||
hasZip &&
|
||||
|
|
@ -303,6 +283,10 @@ async function navigate(
|
|||
queryStringsObject[queryStrings.ZIP_CODE] = zip;
|
||||
}
|
||||
|
||||
if (promo) {
|
||||
queryStringsObject[queryStrings.PROMO] = promo;
|
||||
}
|
||||
|
||||
router.push({
|
||||
name: "root",
|
||||
query: Object.assign(optionalQuery, queryStringsObject),
|
||||
|
|
@ -394,18 +378,26 @@ function arePagePrerequisitesValid(component) {
|
|||
// Run SiteEntry and PageEntry triggers for experiments
|
||||
async function runExperiments(nextPage) {
|
||||
if (!store.getters.applicationUser.triggeredSiteEntry) {
|
||||
await baseMixin.methods.dispatchStoreAction(storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, {
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
|
||||
});
|
||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
|
||||
{
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
|
||||
},
|
||||
nextPage
|
||||
);
|
||||
}
|
||||
|
||||
await baseMixin.methods.dispatchStoreAction(storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, {
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.PAGE_ENTRY,
|
||||
triggerValue: nextPage,
|
||||
});
|
||||
await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.RUN_EXPERIMENTS_FOR_TRIGGER,
|
||||
{
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.PAGE_ENTRY,
|
||||
triggerValue: nextPage,
|
||||
},
|
||||
nextPage
|
||||
);
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
const externalUrls = {
|
||||
HERITAGE_FUNNEL: process.env.VUE_APP_HERITAGE_FUNNEL,
|
||||
SAFELITE_HOP: process.env.VUE_APP_SAFELITE_HOP,
|
||||
PAYPAL_SUCCESS_URL: process.env.VUE_APP_PAYPAL_SUCCESS_URL,
|
||||
PAYPAL_CANCEL_URL: process.env.VUE_APP_PAYPAL_CANCEL_URL,
|
||||
};
|
||||
|
||||
export { externalUrls };
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ const fmgPageValues = {
|
|||
CUSTOMER_DETAILS: "customer-details",
|
||||
REVIEW: "review",
|
||||
PAYMENT_METHOD: "payment-method",
|
||||
PAYMENT: "payment",
|
||||
PAYMENT_PIA_RETURN: "payment-pia-return",
|
||||
CONFIRMATION: "confirmation",
|
||||
};
|
||||
|
||||
export { fmgPageValues };
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ const navigationScenarios = {
|
|||
CLICKED_SERVICE_LOCATION_EDIT: "CLICKED_SERVICE_LOCATION_EDIT",
|
||||
CLICKED_SCHEDULE_EDIT: "CLICKED_SCHEDULE_EDIT",
|
||||
CLICKED_CUSTOMER_EDIT: "CLICKED_CUSTOMER_EDIT",
|
||||
|
||||
// Payment
|
||||
CLICKED_PAY_NOW: "CLICKED_PAY_NOW",
|
||||
PIA_ERROR: "PIA_ERROR",
|
||||
PIA_SUCCESS: "PIA_SUCCESS",
|
||||
};
|
||||
|
||||
export { navigationScenarios };
|
||||
|
|
|
|||
|
|
@ -465,8 +465,58 @@ const routingTable = function (store) {
|
|||
scenario: navigationScenarios.CLICKED_CUSTOMER_EDIT,
|
||||
destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||
destinationFmgPageValue: fmgPageValues.PAYMENT_METHOD,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fmgPageValue: fmgPageValues.PAYMENT_METHOD,
|
||||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_BACK,
|
||||
destinationFmgPageValue: fmgPageValues.REVIEW,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||
destinationFmgPageValue: fmgPageValues.CONFIRMATION,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_PAY_NOW,
|
||||
destinationFmgPageValue: fmgPageValues.PAYMENT,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fmgPageValue: fmgPageValues.PAYMENT,
|
||||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_BACK,
|
||||
destinationFmgPageValue: fmgPageValues.PAYMENT_METHOD,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||
destinationFmgPageValue: fmgPageValues.CONFIRMATION,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fmgPageValue: fmgPageValues.PAYMENT_PIA_RETURN,
|
||||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.PIA_ERROR,
|
||||
destinationFmgPageValue: fmgPageValues.PAYMENT_METHOD,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.PIA_SUCCESS,
|
||||
destinationFmgPageValue: fmgPageValues.CONFIRMATION,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fmgPageValue: fmgPageValues.CONFIRMATION,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
getDisplayTextForDurationLength,
|
||||
} from "@/layouts/schedule/helpers/schedule-helper";
|
||||
|
||||
import { getDateDifferenceInDays } from "@/helpers/date-helper";
|
||||
// Export State
|
||||
const getDefaultState = () => {
|
||||
return {
|
||||
|
|
@ -91,6 +92,8 @@ const getDefaultState = () => {
|
|||
coverageStatus: null,
|
||||
},
|
||||
parentAccountNumber: 0,
|
||||
payNowType: "cc",
|
||||
piaErrorCode: null,
|
||||
},
|
||||
schedule: {
|
||||
date: null,
|
||||
|
|
@ -105,6 +108,7 @@ const getDefaultState = () => {
|
|||
referralDate: null,
|
||||
referralCorrelationId: null,
|
||||
eon: null,
|
||||
workOrderNumber: null,
|
||||
},
|
||||
applicationUser: {
|
||||
eventBus: [],
|
||||
|
|
@ -213,6 +217,15 @@ export const mutations = {
|
|||
updateIsInsurance(state, isInsurance) {
|
||||
state.order.payment.isInsurance = isInsurance;
|
||||
},
|
||||
updatePayNowType(state, payNowType) {
|
||||
state.order.payment.payNowType = payNowType;
|
||||
},
|
||||
updatePiaErrorCode(state, piaErrorCode) {
|
||||
state.order.payment.piaErrorCode = piaErrorCode;
|
||||
},
|
||||
updateWorkOrderNumber(state, workOrderNumber) {
|
||||
state.order.workOrderNumber = workOrderNumber;
|
||||
},
|
||||
updateInsuranceVerifiedStatus(state, isVerified) {
|
||||
state.order.payment.insuranceCoverage.isVerified = isVerified;
|
||||
},
|
||||
|
|
@ -234,8 +247,6 @@ export const mutations = {
|
|||
},
|
||||
updateRegistration(state, registrationInfo) {
|
||||
state.order.vehicle.registration.licensePlate = registrationInfo?.licensePlate;
|
||||
state.order.customer.firstName = registrationInfo?.firstName;
|
||||
state.order.customer.lastName = registrationInfo?.lastName;
|
||||
},
|
||||
updateServiceZip(state, serviceZipInfo) {
|
||||
state.order.serviceLocation.state = serviceZipInfo.state;
|
||||
|
|
@ -505,6 +516,7 @@ export const mutations = {
|
|||
|
||||
// Export Getters
|
||||
export const getters = {
|
||||
submitAfterSave: (state) => state.submitAfterSave,
|
||||
vehicle: (state) => state.order.vehicle,
|
||||
eventBusItem: (state) => (eventCategory, eventSubCategory) => {
|
||||
const matchedEvent = state.applicationUser.eventBus.find(
|
||||
|
|
@ -636,40 +648,62 @@ function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
|||
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
|
||||
}
|
||||
|
||||
function provisionalTriggersToString(provisionalTriggers) {
|
||||
return "ProvisionalTriggers:" + provisionalTriggers.join(",");
|
||||
function getTimeSlotsAdditionalEventData(
|
||||
provisionalTriggers,
|
||||
zipCode,
|
||||
firstAvailableAppointmentDateString,
|
||||
shopAppointmentType
|
||||
) {
|
||||
var numberOfDays = null;
|
||||
if (firstAvailableAppointmentDateString)
|
||||
numberOfDays = getDateDifferenceInDays(new Date(), firstAvailableAppointmentDateString);
|
||||
|
||||
if (shopAppointmentType)
|
||||
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(
|
||||
","
|
||||
)}`;
|
||||
else
|
||||
return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join(
|
||||
","
|
||||
)}`;
|
||||
}
|
||||
|
||||
// Export Actions
|
||||
export const actions = {
|
||||
// Vehicle API Actions
|
||||
getVehicleYears(context) {
|
||||
getVehicleYears(context, { pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetVehicleYears.method,
|
||||
endpoint: endpoints.GetVehicleYears.url,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVehicleByYmms(context, { year, make, model, style }) {
|
||||
lookupVehicleByYmms(context, { payload: { year, make, model, style }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVehicleByYmms.method,
|
||||
endpoint: `${endpoints.LookupVehicleByYmms.url}/${year}/${make}/${model}/${style}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVehicleByVin(context, { vin }) {
|
||||
lookupVehicleByVin(context, { payload: { vin }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVehicleByVin.method,
|
||||
endpoint: endpoints.LookupVehicleByVin.url,
|
||||
payload: {
|
||||
vin: vin, // EX "1J4GW58S4XC541166"
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVinByPlate(context, { licensePlate, licenseState }) {
|
||||
lookupVinByPlate(context, { payload: { licensePlate, licenseState }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVinByPlate.method,
|
||||
endpoint: endpoints.LookupVinByPlate.url,
|
||||
|
|
@ -677,12 +711,17 @@ export const actions = {
|
|||
licensePlate: licensePlate,
|
||||
licenseState: licenseState,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVinByAddress(
|
||||
context,
|
||||
{ licenseLastName, licenseStreetAddress, licenseZip, licenseState }
|
||||
{
|
||||
payload: { licenseLastName, licenseStreetAddress, licenseZip, licenseState },
|
||||
pageNameToLog,
|
||||
}
|
||||
) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVinByAddress.method,
|
||||
|
|
@ -693,10 +732,14 @@ export const actions = {
|
|||
licenseZip: licenseZip,
|
||||
licenseState: licenseState,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
lookupVinByImage(context, image) {
|
||||
lookupVinByImage(context, { payload, pageNameToLog }) {
|
||||
const image = payload;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
|
|
@ -718,68 +761,84 @@ export const actions = {
|
|||
method: endpoints.LookupVinByImage.method,
|
||||
endpoint: endpoints.LookupVinByImage.url,
|
||||
payload: data,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
isVinByAddressPermissible(context, zip) {
|
||||
isVinByAddressPermissible(context, { payload: { zip }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.IsVinByAddressPermissible.method,
|
||||
endpoint: `${endpoints.IsVinByAddressPermissible.url}?zipcode=${zip}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getVehicleMakes(context, { year }) {
|
||||
getVehicleMakes(context, { payload: { year }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetVehicleMakes.method,
|
||||
endpoint: `${endpoints.GetVehicleMakes.url}/${year}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getVehicleModels(context, { year, make }) {
|
||||
getVehicleModels(context, { payload: { year, make }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetVehicleModels.method,
|
||||
endpoint: `${endpoints.GetVehicleModels.url}/${year}/${make}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getVehicleStyles(context, { year, make, model }) {
|
||||
getVehicleStyles(context, { payload: { year, make, model }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetVehicleStyles.method,
|
||||
endpoint: `${endpoints.GetVehicleStyles.url}/${year}/${make}/${model}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getVehicle(context, { year, make, model, style }) {
|
||||
getVehicle(context, { payload: { year, make, model, style }, pageNameToLog }) {
|
||||
return globalMethods
|
||||
.callHttpClient({
|
||||
methods: endpoints.GetVehicle.method,
|
||||
endpoint: `${endpoints.GetVehicle.url}/${year}/${make}/${model}/${style}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
})
|
||||
.then((response) => {
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
||||
getDamageOptions(context, { carId }) {
|
||||
getDamageOptions(context, { payload: { carId }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
methods: endpoints.GetDamageOptions.method,
|
||||
endpoint: `${endpoints.GetDamageOptions.url}/${carId}`,
|
||||
payload: {},
|
||||
additionalSuccessEventDataHandler: (response) =>
|
||||
"QueryStringZip: " + getQuerystringParameter(queryStrings.ZIP_CODE),
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
validateZip(context, { zip }) {
|
||||
validateZip(context, { payload: { zip }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
methods: endpoints.ValidateZip.method,
|
||||
endpoint: `${endpoints.ValidateZip.url}/${zip}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -828,6 +887,8 @@ export const actions = {
|
|||
payload: {
|
||||
pageName: pageName,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageName,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -846,11 +907,16 @@ export const actions = {
|
|||
pageName
|
||||
),
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageName,
|
||||
});
|
||||
},
|
||||
|
||||
// Analytics Actions
|
||||
logExperimentExposure(context, { userId, sessionKey, pageName, experiment }) {
|
||||
logExperimentExposure(
|
||||
context,
|
||||
{ payload: { userId, sessionKey, pageName, experiment }, pageNameToLog }
|
||||
) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LogExperimentExposureIfAssigned.method,
|
||||
endpoint: endpoints.LogExperimentExposureIfAssigned.url,
|
||||
|
|
@ -871,15 +937,19 @@ export const actions = {
|
|||
pageName: pageName,
|
||||
},
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
// Location API Actions
|
||||
getAlertReasonsByCtu(context, { ctu }) {
|
||||
getAlertReasonsByCtu(context, { payload: { ctu }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetAlertReasons.method,
|
||||
endpoint: `${endpoints.GetAlertReasons.url}/${ctu}`,
|
||||
payload: {},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -895,6 +965,7 @@ export const actions = {
|
|||
parentAccountNumber,
|
||||
savedSessionId,
|
||||
crmCustomerId,
|
||||
workOrderNumber,
|
||||
}
|
||||
) {
|
||||
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||
|
|
@ -905,6 +976,7 @@ export const actions = {
|
|||
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccountNumber);
|
||||
context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId);
|
||||
context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId);
|
||||
context.commit(storeMutations.WORK_ORDER_NUMBER, workOrderNumber);
|
||||
},
|
||||
|
||||
logPageView(
|
||||
|
|
@ -1047,7 +1119,10 @@ export const actions = {
|
|||
});
|
||||
},
|
||||
|
||||
async runExperimentsForTrigger(context, { userId, triggerEvent, triggerValue }) {
|
||||
async runExperimentsForTrigger(
|
||||
context,
|
||||
{ payload: { userId, triggerEvent, triggerValue }, pageNameToLog }
|
||||
) {
|
||||
if (triggerEvent == experimentTriggers.SITE_ENTRY) {
|
||||
context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true);
|
||||
}
|
||||
|
|
@ -1064,6 +1139,8 @@ export const actions = {
|
|||
method: endpoints.RunExperimentsForTrigger.method,
|
||||
endpoint: endpoints.RunExperimentsForTrigger.url,
|
||||
payload: payload,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
|
||||
context.commit(storeMutations.UPDATE_EXPERIMENTS, response.data.experiments);
|
||||
|
|
@ -1078,7 +1155,7 @@ export const actions = {
|
|||
},
|
||||
|
||||
// PartsOrQuestions API Actions
|
||||
async getPartsOrQuestions(context) {
|
||||
async getPartsOrQuestions(context, { pageNameToLog }) {
|
||||
const vehicle = context.getters.vehicle;
|
||||
const damage = context.getters.damage;
|
||||
const order = context.state.order;
|
||||
|
|
@ -1100,6 +1177,8 @@ export const actions = {
|
|||
zip: zipCode,
|
||||
vin: vin,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
|
||||
// Flatten location and name properties
|
||||
|
|
@ -1111,7 +1190,7 @@ export const actions = {
|
|||
},
|
||||
|
||||
// Parts API Actions
|
||||
async getParts(context) {
|
||||
async getParts(context, { pageNameToLog }) {
|
||||
const vehicle = context.getters.vehicle;
|
||||
const damage = context.getters.damage;
|
||||
const order = context.state.order;
|
||||
|
|
@ -1136,6 +1215,8 @@ export const actions = {
|
|||
zip: zipCode,
|
||||
vin: vin,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
|
||||
// Flatten location and name properties
|
||||
|
|
@ -1146,13 +1227,15 @@ export const actions = {
|
|||
return response;
|
||||
},
|
||||
|
||||
getWipers(context) {
|
||||
getWipers(context, { pageNameToLog }) {
|
||||
const carId = context.getters.vehicle.carId;
|
||||
const serviceZipCode = context.getters.order.serviceLocation.zipCode;
|
||||
return globalMethods
|
||||
.callHttpClient({
|
||||
method: endpoints.GetWipers.method,
|
||||
endpoint: `${endpoints.GetWipers.url}/${carId}/${serviceZipCode}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
})
|
||||
.catch((error) => {
|
||||
// The wiper service sometimes returns 500s on legitimate carId/zipCode combination - return empty array instead of breaking flow
|
||||
|
|
@ -1160,14 +1243,16 @@ export const actions = {
|
|||
});
|
||||
},
|
||||
|
||||
getRainDefense(context) {
|
||||
getRainDefense(context, { pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetRainDefense.method,
|
||||
endpoint: endpoints.GetRainDefense.url,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getMobileFeePart(context) {
|
||||
getMobileFeePart(context, { pageNameToLog }) {
|
||||
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||
const parentAccountNumber = context.getters.payment.parentAccountNumber;
|
||||
const billToAccountNumber = 87291; // TODO: MAKE THIS REAL
|
||||
|
|
@ -1175,10 +1260,12 @@ export const actions = {
|
|||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetMobileFeePart.method,
|
||||
endpoint: `${endpoints.GetMobileFeePart.url}/${damageType}/${parentAccountNumber}/${billToAccountNumber}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getServiceabilityDetails(context, { serviceZipCode }) {
|
||||
getServiceabilityDetails(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
||||
const lineItemsWithOnlyPartNumbers = context.getters.order.lineItems.supportingItems.map(
|
||||
(lineItem) => ({
|
||||
partNumber: lineItem.partNumber,
|
||||
|
|
@ -1202,20 +1289,24 @@ export const actions = {
|
|||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetServiceabilityDetails.method,
|
||||
endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&carId=${carId}&${lineItems}&${glassPieces}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getProviders(context, { serviceZipCode }) {
|
||||
getProviders(context, { payload: { serviceZipCode }, pageNameToLog }) {
|
||||
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||
const shopRadiusInMiles = 100;
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetProviders.method,
|
||||
endpoint: `${endpoints.GetProviders.url}/${serviceZipCode}/${damageType}/${shopRadiusInMiles}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getSupportingItems(context) {
|
||||
getSupportingItems(context, { pageNameToLog }) {
|
||||
const glassPartsArray = context.getters.lineItems.glassParts ?? [];
|
||||
const carId = context.getters.vehicle.carId;
|
||||
const isRepair = context.getters.damage.isRepair;
|
||||
|
|
@ -1231,17 +1322,22 @@ export const actions = {
|
|||
parts: glassPartsArray,
|
||||
numberOfRepairChips: isRepair ? numberOfChips : 0,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getCapabilityQuestions(context, { carId, partNumber }) {
|
||||
getCapabilityQuestions(context, { payload: { carId, partNumber }, pageNameToLog }) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetCapabilityQuestions.method,
|
||||
endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getPartFromCapabilityQuestionAnswer(context, glassLocation) {
|
||||
getPartFromCapabilityQuestionAnswer(context, { payload, pageNameToLog }) {
|
||||
const glassLocation = payload;
|
||||
const pageData = context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS);
|
||||
|
||||
const part = pageData.partsOrQuestions.find((x) => x.glassLocation === glassLocation)
|
||||
|
|
@ -1258,10 +1354,15 @@ export const actions = {
|
|||
part,
|
||||
capabilityAnswerResults: capabilityQuestionAnswersForPart,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
getShopTimeSlots(context, { startDate, endDate, shopAppointmentType, providerNumber }) {
|
||||
getShopTimeSlots(
|
||||
context,
|
||||
{ payload: { startDate, endDate, shopAppointmentType, providerNumber }, pageNameToLog }
|
||||
) {
|
||||
const order = context.state.order;
|
||||
const vehicle = context.state.order.vehicle;
|
||||
|
||||
|
|
@ -1317,12 +1418,19 @@ export const actions = {
|
|||
method: endpoints.GetShopTimeSlots.method,
|
||||
endpoint: endpoints.GetShopTimeSlots.url,
|
||||
payload: payload,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
additionalSuccessEventDataHandler: (response) =>
|
||||
provisionalTriggersToString(response.data.provisionalTriggers),
|
||||
getTimeSlotsAdditionalEventData(
|
||||
response.data.provisionalTriggers,
|
||||
order.serviceLocation.zipCode,
|
||||
response.data.days?.[0]?.date,
|
||||
shopAppointmentType
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
getMobileTimeSlots(context, { startDate, endDate }) {
|
||||
getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) {
|
||||
const order = context.state.order;
|
||||
const vehicle = context.state.order.vehicle;
|
||||
let lineItems = [
|
||||
|
|
@ -1374,23 +1482,39 @@ export const actions = {
|
|||
method: endpoints.GetMobileTimeSlots.method,
|
||||
endpoint: endpoints.GetMobileTimeSlots.url,
|
||||
payload: payload,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
additionalSuccessEventDataHandler: (response) =>
|
||||
provisionalTriggersToString(response.data.provisionalTriggers),
|
||||
getTimeSlotsAdditionalEventData(
|
||||
response.data.provisionalTriggers,
|
||||
order.serviceLocation.zipCode,
|
||||
response.data.days?.[0]?.date
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
getMobilePremiumFee(context) {
|
||||
getMobilePremiumFee(context, { pageNameToLog }) {
|
||||
const damageType = context.getters.damage.isRepair ? "Repair" : "Replace";
|
||||
const paymentType = context.getters.order.payment.isInsurance ? "Insurance" : "Cash";
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetMobilePremiumFee.method,
|
||||
endpoint: `${endpoints.GetMobilePremiumFee.url}/${paymentType}/${damageType}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
},
|
||||
|
||||
// Signing Service for Payment
|
||||
getSignature() {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetSignature.method,
|
||||
endpoint: endpoints.GetSignature.url,
|
||||
});
|
||||
},
|
||||
|
||||
// Session API Actions
|
||||
saveSession(context) {
|
||||
saveSession(context, { pageNameToLog }) {
|
||||
const vehicle = context.getters.vehicle;
|
||||
const damage = context.getters.damage;
|
||||
const order = context.state.order;
|
||||
|
|
@ -1496,6 +1620,8 @@ export const actions = {
|
|||
eon: order.eon,
|
||||
},
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
additionalSuccessEventDataHandler: (response) =>
|
||||
"Email provided: " + (order.customer.emailAddress ? "true" : "false"),
|
||||
});
|
||||
|
|
@ -1504,12 +1630,15 @@ export const actions = {
|
|||
loadSession(
|
||||
context,
|
||||
{
|
||||
savedSessionId,
|
||||
referralNumber,
|
||||
referralDate,
|
||||
parentAccountNumber,
|
||||
referralCorrelationId,
|
||||
isConceptInsurance,
|
||||
payload: {
|
||||
savedSessionId,
|
||||
referralNumber,
|
||||
referralDate,
|
||||
parentAccountNumber,
|
||||
referralCorrelationId,
|
||||
isConceptInsurance,
|
||||
},
|
||||
pageNameToLog,
|
||||
}
|
||||
) {
|
||||
const order = context.state.order;
|
||||
|
|
@ -1525,6 +1654,8 @@ export const actions = {
|
|||
parentAccountNumber: parentAccountNumber,
|
||||
referralCorrelationId: referralCorrelationId,
|
||||
},
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
})
|
||||
.then(
|
||||
async (response) => {
|
||||
|
|
@ -1547,7 +1678,7 @@ export const actions = {
|
|||
response.data
|
||||
);
|
||||
|
||||
await resetScheduleIfUnavailable(context, response.data.order);
|
||||
await resetScheduleIfUnavailable(context, response.data.order, pageNameToLog);
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
|
|
@ -1560,6 +1691,9 @@ export const actions = {
|
|||
saveWorkOrderFlag(context, submitAfterSave) {
|
||||
context.commit(storeMutations.UPDATE_WORK_ORDER_FLAG, submitAfterSave);
|
||||
},
|
||||
savePiaErrorCode(context, piaErrorCode) {
|
||||
context.commit(storeMutations.UPDATE_PIA_ERROR_CODE, piaErrorCode);
|
||||
},
|
||||
|
||||
// Business domain actions
|
||||
|
||||
|
|
@ -1568,26 +1702,25 @@ export const actions = {
|
|||
context,
|
||||
{ year, make, model, style, carId, category, imageUrl, imageVifNumber, imageVifColor }
|
||||
) {
|
||||
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
|
||||
if (context.state.order.vehicle.year !== year) {
|
||||
if (
|
||||
context.state.order.vehicle.year != year ||
|
||||
context.state.order.vehicle.make != make ||
|
||||
context.state.order.vehicle.model != model ||
|
||||
context.state.order.vehicle.style != style
|
||||
) {
|
||||
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_VIN, null);
|
||||
context.commit(storeMutations.UPDATE_YEAR, year);
|
||||
}
|
||||
if (context.state.order.vehicle.make !== make) {
|
||||
context.commit(storeMutations.UPDATE_MAKE, make);
|
||||
}
|
||||
if (context.state.order.vehicle.model !== model) {
|
||||
context.commit(storeMutations.UPDATE_MODEL, model);
|
||||
}
|
||||
if (context.state.order.vehicle.style !== style) {
|
||||
context.commit(storeMutations.UPDATE_STYLE, style);
|
||||
context.commit(storeMutations.UPDATE_CAR_ID, carId);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, category);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor);
|
||||
}
|
||||
context.commit(storeMutations.UPDATE_CAR_ID, carId);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, category);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor);
|
||||
},
|
||||
|
||||
saveVehicleDamage(
|
||||
|
|
@ -1663,15 +1796,25 @@ export const actions = {
|
|||
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
||||
}
|
||||
|
||||
//Save new values
|
||||
context.commit(storeMutations.UPDATE_VEHICLE, vehicleInfo);
|
||||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||
|
||||
if (registrationInfo) {
|
||||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||
|
||||
// only update name information if there isn't a value in state already
|
||||
if (!context.getters.order.customer.firstName) {
|
||||
context.commit(storeMutations.UPDATE_CUSTOMER_DETAILS, {
|
||||
firstName: registrationInfo.firstName,
|
||||
lastName: registrationInfo.lastName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
saveRegistrationAddressLookup(
|
||||
context,
|
||||
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
|
||||
{ isSelectedGlassAvailableForVehicle, vehicleInfo, customerInfo }
|
||||
) {
|
||||
//Reset dependent state when changing
|
||||
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
|
||||
|
|
@ -1680,10 +1823,16 @@ export const actions = {
|
|||
if (!isSelectedGlassAvailableForVehicle) {
|
||||
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
||||
}
|
||||
}
|
||||
|
||||
//Save new values
|
||||
context.commit(storeMutations.UPDATE_VEHICLE, vehicleInfo);
|
||||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE, vehicleInfo);
|
||||
|
||||
// only update name information if there isn't a value in state already
|
||||
if (!context.getters.order.customer.firstName && customerInfo) {
|
||||
context.commit(storeMutations.UPDATE_CUSTOMER_DETAILS, {
|
||||
firstName: customerInfo.firstName,
|
||||
lastName: customerInfo.lastName,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -1834,6 +1983,10 @@ export const actions = {
|
|||
context.commit(storeMutations.UPDATE_IS_INSURANCE, isInsurance);
|
||||
},
|
||||
|
||||
savePayNowType(context, payNowType) {
|
||||
context.commit(storeMutations.UPDATE_PAY_NOW_TYPE, payNowType);
|
||||
},
|
||||
|
||||
saveParentAccountNumber(context, parentAccountNumber) {
|
||||
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, parentAccountNumber);
|
||||
},
|
||||
|
|
@ -1857,7 +2010,7 @@ export const actions = {
|
|||
// Price order actions
|
||||
async priceOrderItemsAndSaveServerData(
|
||||
context,
|
||||
{ availableLineItems, serviceZipCode, serviceZipCodeCtu }
|
||||
{ payload: { availableLineItems, serviceZipCode, serviceZipCodeCtu }, pageNameToLog }
|
||||
) {
|
||||
const zipCodeToUse = serviceZipCode
|
||||
? serviceZipCode
|
||||
|
|
@ -1897,6 +2050,8 @@ export const actions = {
|
|||
const response = await globalMethods.callHttpClient({
|
||||
method: endpoints.PriceOrderItems.method,
|
||||
endpoint: `${endpoints.PriceOrderItems.url}?${queryString}`,
|
||||
logApiCall: true,
|
||||
pageNameToLog: pageNameToLog,
|
||||
});
|
||||
|
||||
context.commit(storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA, response.data.serverData);
|
||||
|
|
@ -2175,7 +2330,7 @@ function providersEqual(providerA, providerB) {
|
|||
// check to see if we have an appointment date on the order object.
|
||||
// if so, make sure it's not in the past. if in the past, clear schedule info in store.
|
||||
// if date not in past, then call schedule service to verify appointment is still available.
|
||||
async function resetScheduleIfUnavailable(context, order) {
|
||||
async function resetScheduleIfUnavailable(context, order, pageNameToLog) {
|
||||
if (!order.schedule?.date) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -2214,8 +2369,11 @@ async function resetScheduleIfUnavailable(context, order) {
|
|||
newTimeSlotsResponse = await context.dispatch(
|
||||
storeActions.GET_MOBILE_TIME_SLOTS,
|
||||
{
|
||||
startDate: order.schedule.date,
|
||||
endDate: endDate,
|
||||
payload: {
|
||||
startDate: order.schedule.date,
|
||||
endDate: endDate,
|
||||
},
|
||||
pageNameToLog: pageNameToLog,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
|
@ -2257,10 +2415,13 @@ async function resetScheduleIfUnavailable(context, order) {
|
|||
newTimeSlotsResponse = await context.dispatch(
|
||||
storeActions.GET_SHOP_TIME_SLOTS,
|
||||
{
|
||||
startDate: order.schedule.date,
|
||||
endDate: endDate,
|
||||
shopAppointmentType: order.serviceLocation.appointmentType,
|
||||
providerNumber: order.serviceLocation.provider.providerNumber,
|
||||
payload: {
|
||||
startDate: order.schedule.date,
|
||||
endDate: endDate,
|
||||
shopAppointmentType: order.serviceLocation.appointmentType,
|
||||
providerNumber: order.serviceLocation.provider.providerNumber,
|
||||
},
|
||||
pageNameToLog: pageNameToLog,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicleYears(context);
|
||||
const response = await actions.getVehicleYears(context, { pageNameToLog: "test" });
|
||||
|
||||
expect(response.data).toEqual([2023, 2022, 2021]);
|
||||
});
|
||||
|
|
@ -385,13 +385,15 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.lookupVehicleByYmms(
|
||||
context,
|
||||
"2019",
|
||||
"Acura",
|
||||
"ILX",
|
||||
"4 DOOR SEDAN"
|
||||
);
|
||||
const response = await actions.lookupVehicleByYmms(context, {
|
||||
payload: {
|
||||
year: "2019",
|
||||
make: "Acura",
|
||||
model: "ILX",
|
||||
style: "4 DOOR SEDAN",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ carId: "C00000001" });
|
||||
});
|
||||
|
|
@ -406,7 +408,12 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.lookupVehicleByVin(context, "12345678901234567");
|
||||
const response = await actions.lookupVehicleByVin(context, {
|
||||
payload: {
|
||||
vin: "12345678901234567",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ carId: "C0000001" });
|
||||
});
|
||||
|
|
@ -421,7 +428,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.lookupVinByPlate(context, "12345678901234567");
|
||||
const response = await actions.lookupVinByPlate(context, {
|
||||
payload: { licensePlate: "12345678901234567", licenseState: "OH" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ carId: "C00000001" });
|
||||
});
|
||||
|
|
@ -438,7 +448,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const response = await actions.lookupVinByImage(context, image);
|
||||
const response = await actions.lookupVinByImage(context, {
|
||||
payload: image,
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(response.data).toEqual(["1C6JJTAG3NL134044"]);
|
||||
|
|
@ -458,7 +471,9 @@ describe("Actions", () => {
|
|||
// Act
|
||||
|
||||
// Assert
|
||||
await expect(actions.lookupVinByImage(context, image)).rejects.toEqual("An error occurred");
|
||||
await expect(
|
||||
actions.lookupVinByImage(context, { payload: image, pageNameToLog: "test" })
|
||||
).rejects.toEqual("An error occurred");
|
||||
});
|
||||
|
||||
it("getVehicleMakes action, should return makes list", async () => {
|
||||
|
|
@ -471,7 +486,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicleMakes(context, "2019");
|
||||
const response = await actions.getVehicleMakes(context, {
|
||||
payload: { year: "2019" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual(["Acura", "Honda"]);
|
||||
});
|
||||
|
|
@ -486,7 +504,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicleModels(context, "2019", "Acura");
|
||||
const response = await actions.getVehicleModels(context, {
|
||||
payload: { year: "2019", make: "Acura" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual(["ILX", "RDX"]);
|
||||
});
|
||||
|
|
@ -501,7 +522,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicleStyles(context, "2019", "Acura", "ILX");
|
||||
const response = await actions.getVehicleStyles(context, {
|
||||
payload: { year: "2019", make: "Acura", model: "ILX" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ style: "4 DOOR SEDAN" });
|
||||
});
|
||||
|
|
@ -516,7 +540,10 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Assert
|
||||
const response = await actions.getVehicle(context, "C00000000");
|
||||
const response = await actions.getVehicle(context, {
|
||||
payload: { year: "2019", make: "Acura", model: "IDX", style: "4-door sedan" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
expect(response.data).toEqual({ carId: "C00000000", category: "CAR" });
|
||||
});
|
||||
|
|
@ -530,7 +557,10 @@ describe("Actions", () => {
|
|||
return Promise.resolve({ data: ["Windshield", "DriversFrontDoor"] });
|
||||
});
|
||||
|
||||
const response = await actions.getDamageOptions(context, "C00000000");
|
||||
const response = await actions.getDamageOptions(context, {
|
||||
payload: { carId: "C00000000" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(response.data).toEqual(["Windshield", "DriversFrontDoor"]);
|
||||
|
|
@ -552,7 +582,10 @@ describe("Actions", () => {
|
|||
});
|
||||
});
|
||||
|
||||
const response = await actions.validateZip(context, "43212");
|
||||
const response = await actions.validateZip(context, {
|
||||
payload: { zip: "43212" },
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(response.data).toEqual({
|
||||
|
|
@ -730,7 +763,7 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
// Act
|
||||
const response = await actions.saveSession(context);
|
||||
const response = await actions.saveSession(context, { pageNameToLog: "test" });
|
||||
|
||||
// Assert
|
||||
expect(response.data).toEqual({ referralNumber: 123 });
|
||||
|
|
@ -750,7 +783,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
const response = await actions.loadSession(context, {
|
||||
savedSessionId: "",
|
||||
payload: {
|
||||
savedSessionId: "",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -776,7 +812,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
const response = await actions.loadSession(context, {
|
||||
savedSessionId: "",
|
||||
payload: {
|
||||
savedSessionId: "",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -801,7 +840,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
const response = await actions.loadSession(context, {
|
||||
savedSessionId: "",
|
||||
payload: {
|
||||
savedSessionId: "",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -1492,9 +1534,18 @@ describe("Actions", () => {
|
|||
licensePlate: "ABC123",
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
firstName: "test",
|
||||
lastName: "test",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
context.getters = {
|
||||
...getters,
|
||||
order: getters.order(context),
|
||||
};
|
||||
|
||||
const commit = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
|
||||
|
|
@ -1538,9 +1589,18 @@ describe("Actions", () => {
|
|||
address: "123 Main St",
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
firstName: "test",
|
||||
lastName: "test",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
context.getters = {
|
||||
...getters,
|
||||
order: getters.order(context),
|
||||
};
|
||||
|
||||
const commit = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
|
||||
|
|
@ -1552,7 +1612,7 @@ describe("Actions", () => {
|
|||
isCarIdDifferent: true,
|
||||
isSelectedGlassAvailableForVehicle: false,
|
||||
vehicleInfo: { carId: "C010101", vin: "XXXXX" },
|
||||
registrationInfo: { firstName: "abc", lastName: "123" },
|
||||
customerInfo: { firstName: "abc", lastName: "123" },
|
||||
serviceLocationInfo: { state: "CO" },
|
||||
customerEmail: "test@safelite.com",
|
||||
};
|
||||
|
|
@ -1570,7 +1630,6 @@ describe("Actions", () => {
|
|||
);
|
||||
|
||||
expect(commit).toBeCalledWith(storeMutations.UPDATE_VEHICLE, payload.vehicleInfo);
|
||||
expect(commit).toBeCalledWith(storeMutations.UPDATE_REGISTRATION, payload.registrationInfo);
|
||||
});
|
||||
|
||||
it("saveVehicle, should save vehicle info", () => {
|
||||
|
|
@ -1701,7 +1760,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
await actions.runExperimentsForTrigger(context, {
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
payload: {
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -1735,7 +1797,10 @@ describe("Actions", () => {
|
|||
|
||||
// Act
|
||||
await actions.runExperimentsForTrigger(context, {
|
||||
triggerEvent: "NotSiteEntry",
|
||||
payload: {
|
||||
triggerEvent: "NotSiteEntry",
|
||||
},
|
||||
pageNameToLog: "test",
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -2702,6 +2767,17 @@ describe("Actions", () => {
|
|||
});
|
||||
|
||||
describe("Getters", () => {
|
||||
it("submitAfterSave, should return true", () => {
|
||||
// Arrange
|
||||
const storeState = state;
|
||||
|
||||
// Act
|
||||
mutations.updateWorkOrderFlag(storeState, true);
|
||||
|
||||
// Assert
|
||||
expect(getters.submitAfterSave(storeState)).toEqual(true);
|
||||
});
|
||||
|
||||
it("Vehicle getter, should return vehicle data", () => {
|
||||
// Arrange
|
||||
const storeState = state;
|
||||
|
|
|
|||
|
|
@ -5,18 +5,14 @@ body {
|
|||
background-color: #fff;
|
||||
color: #4d5151;
|
||||
.container-fluid {
|
||||
max-width: 576px; //Remove once desktop app is complete
|
||||
&.container-shadow {
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.15); //Use instead of Bootstrap's helper
|
||||
.prevent-squish {
|
||||
overflow-x: unset;
|
||||
}
|
||||
&.make-tall {
|
||||
&.page-container-grouped-styles {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.prevent-squish {
|
||||
overflow-x: unset;
|
||||
}
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
|
|
@ -43,18 +39,6 @@ body {
|
|||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-container-grouped-styles {
|
||||
@extend .container-fluid, .shadow, .rounded-3, .p-2, .position-relative, .make-tall, .px-5;
|
||||
}
|
||||
|
||||
//Footer modal backdrop adjustments for positioning
|
||||
.modal-backdrop {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
max-width: 576px;
|
||||
height: calc(100% - 72px);
|
||||
}
|
||||
}
|
||||
//Disable scroll of main container when modal is open
|
||||
.modal-open {
|
||||
|
|
|
|||
|
|
@ -170,15 +170,6 @@ $spacers: (
|
|||
//Enable negative spacing (does NOT work on padding)
|
||||
$enable-negative-margins: true;
|
||||
|
||||
//Grid breakpoints
|
||||
$grid-breakpoints: (
|
||||
xs: 0,
|
||||
sm: 576px,
|
||||
md: 838px,
|
||||
lg: 1074px,
|
||||
xl: 1416px,
|
||||
);
|
||||
|
||||
//Shadow
|
||||
$box-shadow: 0 0.5rem 1rem rgba($black, 0.15);
|
||||
$box-shadow-sm: 0 0.125rem 0.25rem rgba($black, 0.075);
|
||||
|
|
@ -197,3 +188,11 @@ $modal-backdrop-opacity: 0;
|
|||
|
||||
//Disable default !important behavior
|
||||
$enable-important-utilities: false;
|
||||
|
||||
//Bootstrap Grid Breakpoints - Use these breakpoints only
|
||||
$grid-breakpoints: (
|
||||
xs: 0,
|
||||
md: 768px,
|
||||
xl: 1200px,
|
||||
xxl: 1440px,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -82,8 +82,8 @@ export default {
|
|||
|
||||
img {
|
||||
// svg's should be constructed on the same canvas size/viewbox to ensure they occupy the same space in the DOM. This will allow easy/proper alignment of elements. See exisitng svg's for examples.
|
||||
height: auto;
|
||||
width: 6.5rem;
|
||||
height: 2rem;
|
||||
width: auto;
|
||||
margin-bottom: 2.2rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ export default {
|
|||
outline: none;
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
|
||||
p {
|
||||
color: $gray-600;
|
||||
|
|
|
|||
|
|
@ -1,36 +1,40 @@
|
|||
process.env.VUE_APP_CONSUMER_CF_DISTRO =
|
||||
"https://digitalapi.dev.safelite.io";
|
||||
process.env.VUE_APP_HERITAGE_FUNNEL =
|
||||
"http://localhost:38000/default.aspx";
|
||||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY =
|
||||
"AIzaSyDptGCkOPgN2uWJOy4ou4M33phRD4MAoJo";
|
||||
process.env.VUE_APP_CONSUMER_CF_DISTRO = "https://digitalapi.dev.safelite.io";
|
||||
process.env.VUE_APP_HERITAGE_FUNNEL = "http://localhost:38000/default.aspx";
|
||||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "AIzaSyDptGCkOPgN2uWJOy4ou4M33phRD4MAoJo";
|
||||
process.env.VUE_APP_CURRENT_ENVIRONMENT = "Localhost";
|
||||
process.env.VUE_APP_MY_ACCOUNT = "https://myaccountdev.safelite.com/";
|
||||
|
||||
//process.env.VUE_APP_SAFELITE_HOP = "http://localhost:60966/fmgCheckoutEmbedded.aspx";
|
||||
process.env.VUE_APP_SAFELITE_HOP = "https://sv2-safelitehop-sys.safelite.com/fmgCheckoutEmbedded.aspx";
|
||||
process.env.VUE_APP_PAYPAL_SUCCESS_URL = "http://localhost:8080/fmg/?fmgPage=payment-pia-return&src=concept-funnel";
|
||||
process.env.VUE_APP_PAYPAL_CANCEL_URL = "http://localhost:8080/fmg/?fmgPage=payment-method&src=concept-funnel";
|
||||
|
||||
// GA & GTM
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY = "(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl+ '>m_auth=amlAYNhxUxuskQo7jmjadg>m_preview=env-38>m_cookies_win=x';f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-M6XCRH');";
|
||||
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC = "https://www.googletagmanager.com/ns.html?id=GTM-M6XCRH>m_auth=amlAYNhxUxuskQo7jmjadg>m_preview=env-38>m_cookies_win=x";
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY =
|
||||
"(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl+ '>m_auth=amlAYNhxUxuskQo7jmjadg>m_preview=env-38>m_cookies_win=x';f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-M6XCRH');";
|
||||
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC =
|
||||
"https://www.googletagmanager.com/ns.html?id=GTM-M6XCRH>m_auth=amlAYNhxUxuskQo7jmjadg>m_preview=env-38>m_cookies_win=x";
|
||||
|
||||
module.exports = {
|
||||
outputDir: "dist/fmg",
|
||||
publicPath: "/fmg",
|
||||
css: {
|
||||
loaderOptions: {
|
||||
sass: {
|
||||
// Load Order Matters!!!
|
||||
// Note: only include functions, variables and mixins here
|
||||
prependData: `
|
||||
outputDir: "dist/fmg",
|
||||
publicPath: "/fmg",
|
||||
css: {
|
||||
loaderOptions: {
|
||||
sass: {
|
||||
// Load Order Matters!!!
|
||||
// Note: only include functions, variables and mixins here
|
||||
prependData: `
|
||||
@import "./node_modules/bootstrap/scss/functions";
|
||||
@import "@/styles/ux-variables.scss";
|
||||
@import "./node_modules/bootstrap/scss/variables";
|
||||
@import "./node_modules/bootstrap/scss/mixins";
|
||||
@import "@/styles/mixins/customMixins";
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
configureWebpack: {
|
||||
devtool: "source-map",
|
||||
},
|
||||
},
|
||||
configureWebpack: {
|
||||
devtool: 'source-map'
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,27 +2,33 @@ process.env.VUE_APP_CONSUMER_CF_DISTRO = "__VUE_APP_CONSUMER_CF_DISTRO__";
|
|||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "__VUE_APP_GOOGLE_PLACES_API_KEY__";
|
||||
process.env.VUE_APP_HERITAGE_FUNNEL = "__VUE_APP_HERITAGE_FUNNEL__";
|
||||
process.env.VUE_APP_CURRENT_ENVIRONMENT = "__VUE_APP_CURRENT_ENVIRONMENT__";
|
||||
process.env.VUE_APP_MY_ACCOUNT = "__VUE_APP_MY_ACCOUNT__";
|
||||
|
||||
process.env.VUE_APP_SAFELITE_HOP = "__VUE_APP_SAFELITE_HOP__";
|
||||
process.env.VUE_APP_PAYPAL_SUCCESS_URL = "__VUE_APP_PAYPAL_SUCCESS_URL__";
|
||||
process.env.VUE_APP_PAYPAL_CANCEL_URL = "__VUE_APP_PAYPAL_CANCEL_URL__";
|
||||
|
||||
// GA & GTM
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY = "__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__";
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC = "__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__"
|
||||
process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC =
|
||||
"__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__";
|
||||
|
||||
module.exports = {
|
||||
outputDir: "dist/fmg",
|
||||
publicPath: "/fmg",
|
||||
css: {
|
||||
loaderOptions: {
|
||||
sass: {
|
||||
// Load Order Matters!!!
|
||||
// Note: only include functions, variables and mixins here
|
||||
prependData: `
|
||||
outputDir: "dist/fmg",
|
||||
publicPath: "/fmg",
|
||||
css: {
|
||||
loaderOptions: {
|
||||
sass: {
|
||||
// Load Order Matters!!!
|
||||
// Note: only include functions, variables and mixins here
|
||||
prependData: `
|
||||
@import "./node_modules/bootstrap/scss/functions";
|
||||
@import "@/styles/ux-variables.scss";
|
||||
@import "./node_modules/bootstrap/scss/variables";
|
||||
@import "./node_modules/bootstrap/scss/mixins";
|
||||
@import "@/styles/mixins/customMixins";
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue