Merge branch 'develop' into feature/CSR-1389
This commit is contained in:
commit
41a63aa4be
60 changed files with 2689 additions and 500 deletions
|
|
@ -1,291 +1,297 @@
|
|||
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_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_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_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_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,9 +27,9 @@ module.exports = {
|
|||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 80,
|
||||
statements: 77,
|
||||
},
|
||||
},
|
||||
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit
|
||||
// silent: true,
|
||||
silent: true,
|
||||
};
|
||||
|
|
|
|||
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,17 @@ const applicationConfig = {
|
|||
PAGE_QUERYSTRING: "fmgPage",
|
||||
SITE_ENTRY_TRIGGER_VALUE: "FixMyGlass",
|
||||
CASH_PARENT_ACCOUNT_NUMBER: 167132,
|
||||
MY_ACCOUNT: process.env.VUE_APP_MY_ACCOUNT,
|
||||
PAYPAL_SUCCESS_URL:
|
||||
location.protocol +
|
||||
"//" +
|
||||
location.host +
|
||||
"/fmg/?fmgPage=payment-pia-return&src=concept-funnel",
|
||||
PAYPAL_CANCEL_URL:
|
||||
location.protocol +
|
||||
"//" +
|
||||
location.host +
|
||||
"/fmg/?fmgPage=payment-method&src=concept-funnel",
|
||||
};
|
||||
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ const queryStrings = {
|
|||
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,8 @@ const storeActions = {
|
|||
SAVE_CUSTOMER_DETAILS: "saveCustomerDetails",
|
||||
SAVE_SERVICE_LOCATION_TECH_NOTES: "saveServiceLocationTechNotes",
|
||||
SAVE_WORK_ORDER_FLAG: "saveWorkOrderFlag",
|
||||
SAVE_PIA_ERROR_CODE: "savePiaErrorCode",
|
||||
SAVE_PIA_WORK_ORDER: "savePiaWorkOrder",
|
||||
};
|
||||
|
||||
export { storeActions };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
const storeMutations = {
|
||||
UPDATE_WORK_ORDER_FLAG: "updateWorkOrderFlag",
|
||||
UPDATE_PIA_ERROR_CODE: "updatePiaErrorCode",
|
||||
UPDATE_PIA_WORK_ORDER: "updatePiaWorkOrder",
|
||||
|
||||
// VEHICLE MUTATIONS
|
||||
UPDATE_YEAR: "updateYear",
|
||||
|
|
@ -46,6 +48,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,11 +192,17 @@ export default {
|
|||
classes = "d-flex flex-row p-0";
|
||||
break;
|
||||
case "listCard":
|
||||
classes = "row g-2 g-md-5 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";
|
||||
}
|
||||
break;
|
||||
case "radio":
|
||||
classes = "ui-radio d-flex";
|
||||
break;
|
||||
|
|
@ -298,6 +304,19 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.vehicle-damage {
|
||||
.button-question {
|
||||
@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);
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
loaderColor="blue"
|
||||
loaderPosition="center" />
|
||||
<div class="row form-test-error" id="date-of-month-error">
|
||||
<ErrorMessage name="date-of-month" class="small mt-1"></ErrorMessage>
|
||||
<ErrorMessage :name="customComponentId" class="small mt-1"></ErrorMessage>
|
||||
</div>
|
||||
<button
|
||||
v-if="calendarViewDirection === 'future' && !disableViewMoreDatesButton"
|
||||
|
|
|
|||
|
|
@ -164,7 +164,6 @@ export default {
|
|||
@include media-breakpoint-up(md) {
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
height: 100vh;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.modal-body {
|
||||
|
|
@ -181,12 +180,16 @@ export default {
|
|||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
overflow: scroll;
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.modal-dialog-centered {
|
||||
align-items: flex-end;
|
||||
min-height: 100%;
|
||||
@include media-breakpoint-up(md) {
|
||||
align-items: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal-footer {
|
||||
|
|
@ -195,6 +198,10 @@ export default {
|
|||
button {
|
||||
margin: 0;
|
||||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.show .modal-dialog {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
cartItems: 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 not found in the cart
|
||||
return !this.cartItems.some((vap) => {
|
||||
return vap.partType === "RAIN DEFENSE";
|
||||
});
|
||||
},
|
||||
showAddWipers() {
|
||||
// if not found in the cart
|
||||
return !this.cartItems.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>
|
||||
369
src/fmg-components/cart/cart.vue
Normal file
369
src/fmg-components/cart/cart.vue
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
<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">${{ amountDue }}</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>
|
||||
<textLink
|
||||
ref="removeLink"
|
||||
linkType="text"
|
||||
text="showMoreShopsLinkText"
|
||||
href="#!"
|
||||
@click-event="removeItem(lineItem)" />
|
||||
</div>
|
||||
<hr style="background: red" />
|
||||
<div
|
||||
v-for="(lineItem, i) in displayedNonPackageLineItems"
|
||||
:key="i"
|
||||
:class="[this.displayedNonPackageLineItems ? 'vaps' : '']">
|
||||
<span>{{ lineItem.name }}</span>
|
||||
<textLink
|
||||
ref="removeLink"
|
||||
linkType="text"
|
||||
text="showMoreShopsLinkText"
|
||||
href="#!"
|
||||
@click-event="removeItem(lineItem)" />
|
||||
<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 textLink from "@/ux-components/text-link/text-link";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
|
||||
export default {
|
||||
name: "modal",
|
||||
props: {
|
||||
headerCmsWidgetName: String,
|
||||
servicePackageOptionsCmsName: String,
|
||||
damage: Object,
|
||||
storeLineItems: Object,
|
||||
availableLineItems: Object,
|
||||
allowableCartItems: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
//availableVaps: [], TO DO Do we need this?
|
||||
packageWithVAPS: false,
|
||||
// packageLevel: "",
|
||||
amountDue: this.getAmountDue(),
|
||||
};
|
||||
},
|
||||
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;
|
||||
},
|
||||
getAmountDue() {
|
||||
return baseMixin.methods.getAmountDue(this.storeLineItems);
|
||||
},
|
||||
removeItem(item) {
|
||||
this.$emit("cart-remove", item);
|
||||
},
|
||||
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.storeLineItems?
|
||||
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.storeLineItems?.vaps;
|
||||
return vaps?.length > 0 ? vaps : [];
|
||||
},
|
||||
supportingItems() {
|
||||
const supportingItems = this.storeLineItems?.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,
|
||||
textLink,
|
||||
},
|
||||
};
|
||||
</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">
|
||||
|
|
@ -102,7 +102,7 @@ export default {
|
|||
},
|
||||
show() {
|
||||
this.isActive = true;
|
||||
this.scrollToPageTop();
|
||||
window.scrollTo({ top: 0, left: 0, behavior: "smooth" });
|
||||
},
|
||||
hide() {
|
||||
var self = this;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
<div :class="`questions-page`">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<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">
|
||||
<div class="col-md-6">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
|
@ -23,7 +23,10 @@
|
|||
: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"
|
||||
|
|
|
|||
|
|
@ -8,3 +8,57 @@ export function getDateDifferenceInDays(startDate, endDate) {
|
|||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,13 @@ export async function saveSession({ pageNameToLog, shouldAwaitSaveSessionQueue =
|
|||
}
|
||||
}
|
||||
|
||||
export async function submitWorkOrder({ pageNameToLog }) {
|
||||
await saveSession({
|
||||
pageNameToLog: pageNameToLog,
|
||||
shouldAwaitSaveSessionQueue: true,
|
||||
});
|
||||
}
|
||||
|
||||
// PRIVATE FUNCTIONS //
|
||||
|
||||
/*
|
||||
|
|
@ -128,6 +135,7 @@ async function saveSessionHelper(pageNameToLog) {
|
|||
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,
|
||||
|
|
@ -239,3 +243,111 @@ describe("saveSession", () => {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
v-slot="{ meta }"
|
||||
autocomplete="off">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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", () => ({
|
||||
|
|
@ -54,6 +55,14 @@ beforeEach(() => {
|
|||
isRepair: false,
|
||||
},
|
||||
referralNumber: "1234567",
|
||||
vehicle: {
|
||||
year: "2004",
|
||||
make: "Ford",
|
||||
model: "F Series F250",
|
||||
},
|
||||
customer: {
|
||||
emailAddress: "test@test.com",
|
||||
},
|
||||
},
|
||||
payment: {
|
||||
isInsurance: true,
|
||||
|
|
@ -71,9 +80,19 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
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();
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
|
@ -82,20 +101,33 @@ describe("confirmation.vue", () => {
|
|||
//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();
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks() {
|
||||
const wrapper = shallowMount(
|
||||
confirmation,
|
||||
getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
navigate: jest.fn(),
|
||||
navigateWithSaving: jest.fn(),
|
||||
navigateWithoutSaving: jest.fn(),
|
||||
},
|
||||
})
|
||||
);
|
||||
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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,77 @@
|
|||
<template>
|
||||
<div class="page-container-grouped-styles position-relative">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<div>
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
isBackButtonHidden="true"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
:buttonSize="true" />
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<div class="page-container-grouped-styles position-relative">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
<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.ScheduleDateFormatted }}</p>
|
||||
<p>{{ this.ScheduleTimeFormatted }}</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.AppointmentWordingText"></div>
|
||||
</div>
|
||||
<div class="emailtext" v-html="this.ConfirmationEmailText"></div>
|
||||
<navbar
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
ref="navbar"
|
||||
isBackButtonHidden="true"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
:buttonSize="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</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 { Form } from "vee-validate";
|
||||
import store from "@/store";
|
||||
|
||||
import {
|
||||
getFullDayName,
|
||||
getFullMonthName,
|
||||
get12HourTimeFormat,
|
||||
get12HourTimeMobileFormat,
|
||||
} from "@/helpers/date-helper";
|
||||
export default {
|
||||
name: "confirmation",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -41,12 +87,123 @@ export default {
|
|||
];
|
||||
|
||||
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")
|
||||
.replaceAll("{custom:MY_ACCOUNT_URL}", applicationConfig.MY_ACCOUNT)
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">");
|
||||
},
|
||||
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;
|
||||
},
|
||||
AppointmentWordingText() {
|
||||
if (this.AppointmentType == AppointmentTypeStrings.MOBILE) {
|
||||
return this.MobileWordingText.replaceAll(
|
||||
"{custom:ADDRESS}",
|
||||
this.ServiceLocationFullAddress
|
||||
);
|
||||
} else if (this.AppointmentType == AppointmentTypeStrings.DROP_OFF) {
|
||||
return this.DropOffWordingText.replaceAll(
|
||||
"{custom:ADDRESS}",
|
||||
this.ProviderFullAddress
|
||||
);
|
||||
} else {
|
||||
return this.InShopWordingText.replaceAll(
|
||||
"{custom:ADDRESS}",
|
||||
this.ProviderFullAddress
|
||||
);
|
||||
}
|
||||
},
|
||||
ServiceLocationFullAddress() {
|
||||
return `${this.ServiceLocationAddress2 ? this.ServiceLocationAddress2 + "," : ""} ${
|
||||
this.ServiceLocationAddress
|
||||
},<br/> ${this.ServiceLocationCity}, ${this.ServiceLocationState} ${
|
||||
this.ServiceLocationZipCode
|
||||
}`;
|
||||
},
|
||||
ProviderFullAddress() {
|
||||
return `${this.ProviderAddress},<br/> ${this.ProviderCity}, ${this.ProviderState} ${this.ProviderZipCode}`;
|
||||
},
|
||||
ScheduleDateFormatted() {
|
||||
const scheduleDate = new Date(this.ScheduleDate);
|
||||
return `${getFullDayName(scheduleDate)}, ${getFullMonthName(
|
||||
scheduleDate
|
||||
)} ${scheduleDate.getDate()}`;
|
||||
},
|
||||
ScheduleTimeFormatted() {
|
||||
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)}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
workOrderNumber: store.getters.order.workOrderNumber,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// Service Location
|
||||
|
|
@ -73,15 +230,101 @@ export default {
|
|||
|
||||
// Schedule
|
||||
const schedule = store.getters.order.schedule;
|
||||
const scheduleReqs = !!(schedule.date && schedule.startTime); //removing schedule.endTime for testing
|
||||
const scheduleReqs = !!(schedule.date && schedule.startTime && schedule.endTime);
|
||||
|
||||
return serviceLocationReqs && scheduleReqs;
|
||||
},
|
||||
forwardButtonAction() {},
|
||||
forwardButtonAction() {
|
||||
window.location.assign("//www.safelite.com/");
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
Form,
|
||||
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,13 +1,13 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<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">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="my-5" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
|
|
@ -82,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
|
||||
|
|
@ -178,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);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<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">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" />
|
||||
<funnelSubHeader class="pb-4" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
<div v-if="!skipVin">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4 mt-5"
|
||||
cmsWidgetName="LicensePlateNumberQuestionWidget"
|
||||
v-model="licensePlate"
|
||||
isRequired
|
||||
|
|
@ -22,6 +23,7 @@
|
|||
validationRules="license-plate-required" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="RegistrationZipQuestionWidget"
|
||||
v-model="registrationZipCode"
|
||||
customInputId="zip"
|
||||
|
|
|
|||
|
|
@ -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,19 +1,89 @@
|
|||
<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" />
|
||||
<loadingModal notFullScreen ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
<paymentMethodQuestion v-if="!isPiaDisabled" v-model="paymentMethodInternalModel" />
|
||||
<div class="fade-on-route-transition sub-container make-tall">
|
||||
<navbar
|
||||
ref="funnelFooter"
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
:isBackButtonHidden="shouldHideBackButton"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction" />
|
||||
</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"
|
||||
:storeLineItems="storeLineItems"
|
||||
:availableLineItems="cartItems"
|
||||
servicePackageOptionsCmsName="ServicePackageTitle"
|
||||
@cart-remove="cartRemove" />
|
||||
|
||||
<div>
|
||||
<alert
|
||||
ref="paypalErrorAlert"
|
||||
class="my-4"
|
||||
v-if="displayPaypalAlert"
|
||||
cmsWidgetName="PaypalErrorAlertWidget"
|
||||
alertClass="alert-danger"
|
||||
v-bind:isDismissible="false" />
|
||||
</div>
|
||||
|
||||
<add-vaps-modal-buttons
|
||||
:cartItems="cartItems"
|
||||
:wiperItems="availableWipers"
|
||||
:rainDefenseItem="availableRainDefense"
|
||||
vapsTilesCmsName="VapsProductTiles" />
|
||||
|
||||
<div>
|
||||
<button v-on:click="ccdPayment">
|
||||
<img
|
||||
src="data:image/png;base64,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"
|
||||
alt="We accept Visa" />
|
||||
</button>
|
||||
<button v-on:click="ccdPayment">
|
||||
<img
|
||||
src="data:image/png;base64,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"
|
||||
alt="Mastercard" />
|
||||
</button>
|
||||
<button v-on:click="ccdPayment">
|
||||
<img
|
||||
src="data:image/png;base64,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"
|
||||
alt="American Express" />
|
||||
</button>
|
||||
<button v-on:click="ccdPayment">
|
||||
<img
|
||||
src="data:image/png;base64,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"
|
||||
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>
|
||||
|
|
@ -25,52 +95,235 @@ 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 paymentMethodQuestion from "@/layouts/payment-method/payment-method-question/payment-method-question";
|
||||
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";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import store from "@/store";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { paymentMethods } from "@/constants/payment-method-constants";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
|
||||
// Validation
|
||||
import { Form } from "vee-validate";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
|
||||
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
|
||||
);
|
||||
const storeLineItems = store.getters.lineItems;
|
||||
const cartItems = [
|
||||
...clonedGlassParts,
|
||||
...storeLineItems.supportingItems,
|
||||
...storeLineItems.vaps,
|
||||
];
|
||||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
vm.availableLineItems = pricingResults;
|
||||
vm.availableWipers = resultMap.wipers;
|
||||
vm.availableRainDefense = resultMap.rainDefense;
|
||||
vm.cartItems = cartItems;
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cartItems: [],
|
||||
availableWipers: null,
|
||||
availableRainDefense: null,
|
||||
availableLineItems: null,
|
||||
supportingItems: null,
|
||||
pricedGlassParts: null,
|
||||
displayPaypalAlert: this.getPaypalAlert(),
|
||||
paymentMethodInternalModel: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
// TODO
|
||||
return true;
|
||||
// Line Items
|
||||
const packageReqs = !!store.getters.order.lineItems.supportingItems;
|
||||
|
||||
// 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);
|
||||
|
||||
// Insurance
|
||||
const isInsuranceSet = store.getters.order.payment.isInsurance !== null;
|
||||
|
||||
// Schedule
|
||||
const schedule = store.getters.order.schedule;
|
||||
const scheduleReqs = !!(
|
||||
schedule.date &&
|
||||
schedule.startTime &&
|
||||
(!isMobile || schedule.endTime) &&
|
||||
schedule.jobMaxMinutes &&
|
||||
schedule.jobMinMinutes
|
||||
);
|
||||
|
||||
// Customer
|
||||
const customer = store.getters.order.customer;
|
||||
const customerReqs = !!(
|
||||
customer.firstName &&
|
||||
customer.lastName &&
|
||||
customer.phoneNumber &&
|
||||
customer.emailAddress
|
||||
);
|
||||
|
||||
return (
|
||||
packageReqs && serviceLocationReqs && isInsuranceSet && scheduleReqs && customerReqs
|
||||
);
|
||||
},
|
||||
backButtonAction() {},
|
||||
forwardButtonAction() {
|
||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||
getPaypalAlert() {
|
||||
return store.getters.order.payment.piaErrorCode ? true : false;
|
||||
},
|
||||
vapsItemsSelectedAction(vapsItemsSelected) {
|
||||
this.cartItems = vapsItemsSelected;
|
||||
},
|
||||
// TODO (as part of CSR-1384) enable adding to the cart
|
||||
// vapsItemsSelectedAction(vapsItemsSelected) {
|
||||
// this.cartItems = vapsItemsSelected;
|
||||
// },
|
||||
cartRemove(item) {
|
||||
const matchedIndices = [];
|
||||
this.cartItems.forEach((cartItem, i) => {
|
||||
if (cartItem.partType === item.partType) {
|
||||
console.log("adding this to matchedIndices: ", i);
|
||||
matchedIndices.push(i);
|
||||
}
|
||||
});
|
||||
this.cartItems = this.cartItems.filter((cartItem, index) => {
|
||||
return cartItem.partType !== item.partType;
|
||||
});
|
||||
},
|
||||
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() {
|
||||
this.piaSetup("cc");
|
||||
},
|
||||
async paypal() {
|
||||
this.piaSetup("pp");
|
||||
},
|
||||
async afterpay() {
|
||||
this.piaSetup("ap");
|
||||
},
|
||||
async piaSetup(payNowType) {
|
||||
this.$refs.loadingModal.showModal();
|
||||
|
||||
//leaving site, clear any save session promises that we will not be able to resolve when we return
|
||||
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||
await this.dispatchStoreAction(storeActions.SAVE_PAY_NOW_TYPE, payNowType, false);
|
||||
|
||||
// make sure pia error codes are reset, and get a pia work order in delete status
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, null, false);
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_WORK_ORDER, true);
|
||||
|
||||
if (!store.getters.order.workOrderNumber) {
|
||||
await submitWorkOrder({ pageNameToLog: "payment-method" });
|
||||
}
|
||||
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_PAY_NOW,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
damageInfo() {
|
||||
return this.$store.getters.damage;
|
||||
},
|
||||
storeLineItems() {
|
||||
// console.log("storeLineItems, aka lineItems from store: ", this.$store.getters.lineItems)
|
||||
return this.$store.getters.lineItems;
|
||||
},
|
||||
customCtaCopy() {
|
||||
switch (this.paymentMethod) {
|
||||
case paymentMethods.CREDIT_CARD:
|
||||
|
|
@ -109,9 +362,11 @@ export default {
|
|||
navbar,
|
||||
funnelSubHeader,
|
||||
Form,
|
||||
loadingModal,
|
||||
cart,
|
||||
alert,
|
||||
addVapsModalButtons,
|
||||
paymentMethodQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
|
|
|
|||
81
src/layouts/payment-pia-return/payment-pia-return.vue
Normal file
81
src/layouts/payment-pia-return/payment-pia-return.vue
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<loadingModal notFullScreen ref="loadingModal" />
|
||||
</Form>
|
||||
</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";
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
||||
import { Form } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "payment-pia-return",
|
||||
async mounted() {
|
||||
this.$refs.loadingModal.showModal();
|
||||
const piaError = getQuerystringParameter(queryStrings.ERROR);
|
||||
const token = getQuerystringParameter(queryStrings.TOKEN);
|
||||
const payerId = getQuerystringParameter(queryStrings.PAYERID);
|
||||
|
||||
if (piaError) {
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PIA_ERROR_CODE,
|
||||
piaError,
|
||||
false
|
||||
);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.PIA_ERROR, this.$route);
|
||||
} else {
|
||||
if (token && payerId) {
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PIA_ERROR_CODE,
|
||||
null,
|
||||
false
|
||||
);
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_WORK_ORDER_FLAG,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
// Final work order submit after returning from PIA.
|
||||
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
||||
await submitWorkOrder({ pageNameToLog: "payment-pia-return" });
|
||||
this.$refs.loadingModal.isModalVisible = false;
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.PIA_SUCCESS,
|
||||
this.$route
|
||||
);
|
||||
} else {
|
||||
baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.SAVE_PIA_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: {
|
||||
loadingModal,
|
||||
Form,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.confirm {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
388
src/layouts/payment/payment.vue
Normal file
388
src/layouts/payment/payment.vue
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
<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="piaLineItems" />
|
||||
|
||||
<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="amountDue" />
|
||||
<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";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
// Validation
|
||||
import { Form } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "payment",
|
||||
data() {
|
||||
return {
|
||||
checkoutUrl: externalUrls.SAFELITE_HOP,
|
||||
payPalSuccessUrl: applicationConfig.PAYPAL_SUCCESS_URL,
|
||||
payPalCancelUrl: applicationConfig.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,
|
||||
workOrderNumber: this.getWOrkOrderNumber(),
|
||||
invoiceNumber: this.getInvoiceNumber(),
|
||||
totalAmount: this.getAmountDue(),
|
||||
displayAmount: this.getDisplayAmountDue(),
|
||||
piaLineItems: this.getPiaLineItems(),
|
||||
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;
|
||||
},
|
||||
getWOrkOrderNumber() {
|
||||
if (store.getters.order.workOrderNumber) {
|
||||
var items = store.getters.order.workOrderNumber.split("-");
|
||||
return items[1];
|
||||
}
|
||||
|
||||
// no work order, set pia error and return
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, "NO WORK ORDER", false);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
getInvoiceNumber() {
|
||||
if (store.getters.order.workOrderNumber) {
|
||||
return store.getters.order.workOrderNumber.replace("-", "");
|
||||
}
|
||||
|
||||
// no work order, set pia error and return
|
||||
this.dispatchStoreAction(storeActions.SAVE_PIA_ERROR_CODE, "NO WORK ORDER", false);
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
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 "";
|
||||
},
|
||||
getPiaLineItems() {
|
||||
const itemsClone = { ...store.getters.order.lineItems };
|
||||
delete itemsClone.serverData;
|
||||
|
||||
var itemsForPia = "";
|
||||
for (var propertyName in itemsClone) {
|
||||
for (var item in itemsClone[propertyName]) {
|
||||
var amount =
|
||||
+itemsClone[propertyName][item].laborAmount +
|
||||
+itemsClone[propertyName][item].sellingPrice;
|
||||
itemsForPia += itemsClone[propertyName][item].partType + "|" + amount + "|1|";
|
||||
}
|
||||
}
|
||||
return itemsForPia;
|
||||
},
|
||||
getAmountDue() {
|
||||
return baseMixin.methods.getAmountDue(store.getters.order.lineItems);
|
||||
},
|
||||
getDisplayAmountDue() {
|
||||
return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems);
|
||||
},
|
||||
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>
|
||||
|
|
@ -2,13 +2,13 @@
|
|||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<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">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
|
@ -146,7 +146,6 @@ export default {
|
|||
resultMap.supportingItems,
|
||||
false
|
||||
);
|
||||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||
<!-- When customer-details is added: v-slot="{ meta }" -->
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<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">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
|
@ -125,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) {
|
||||
|
|
@ -174,8 +177,7 @@ export default {
|
|||
// damageReqs handles checking for damage, even though it is also required for this section.
|
||||
const packageReqs = !!(
|
||||
(damage.isRepair || lineItems.glassParts) &&
|
||||
lineItems.supportingItems &&
|
||||
lineItems.vaps
|
||||
lineItems.supportingItems
|
||||
);
|
||||
|
||||
// Service Location
|
||||
|
|
@ -205,7 +207,7 @@ export default {
|
|||
const scheduleReqs = !!(
|
||||
schedule.date &&
|
||||
schedule.startTime &&
|
||||
schedule.endTime &&
|
||||
(!isMobile || schedule.endTime) &&
|
||||
schedule.jobMaxMinutes &&
|
||||
schedule.jobMinMinutes
|
||||
);
|
||||
|
|
@ -231,7 +233,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,
|
||||
|
|
@ -310,6 +317,7 @@ export default {
|
|||
serviceLocationReview,
|
||||
scheduleReview,
|
||||
customerReview,
|
||||
Form,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<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">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" class="mt-5" />
|
||||
<template v-if="ChangeShopLink.length">
|
||||
<textBlock
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
:answers="answersToDisplay"
|
||||
:groupName="groupName"
|
||||
buttonTypeString="listCard"
|
||||
v-model="selectedValues"
|
||||
v-model="selectedValue"
|
||||
:suppressError="suppressError"
|
||||
:validationRules="validationRules"
|
||||
isRequired />
|
||||
|
|
@ -55,7 +55,7 @@ export default {
|
|||
})
|
||||
: [];
|
||||
},
|
||||
selectedValues: {
|
||||
selectedValue: {
|
||||
get: function () {
|
||||
return this.modelValue;
|
||||
},
|
||||
|
|
|
|||
15
src/layouts/service-location/classes/provider-address.js
Normal file
15
src/layouts/service-location/classes/provider-address.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
export class ProviderAddress {
|
||||
constructor(
|
||||
streetAddress = null,
|
||||
city = null,
|
||||
state = null,
|
||||
zipCode = null,
|
||||
zipCodeCtu = null
|
||||
) {
|
||||
this.streetAddress = streetAddress;
|
||||
this.city = city;
|
||||
this.state = state;
|
||||
this.zipCode = zipCode;
|
||||
this.zipCodeCtu = zipCodeCtu;
|
||||
}
|
||||
}
|
||||
8
src/layouts/service-location/classes/provider.js
Normal file
8
src/layouts/service-location/classes/provider.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { ProviderAddress } from "./provider-address";
|
||||
|
||||
export class Provider {
|
||||
constructor(providerNumber = null, address = null) {
|
||||
this.providerNumber = providerNumber;
|
||||
this.address = address ?? new ProviderAddress();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { storeActions } from "@/constants/store-actions";
|
||||
import store from "@/store";
|
||||
import baseMixin from "@/mixins/base-mixin.js";
|
||||
|
||||
export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
|
||||
|
|
|
|||
|
|
@ -314,9 +314,10 @@ export default {
|
|||
margin-top: 0em;
|
||||
}
|
||||
|
||||
#mobileLocationLinkPromptId {
|
||||
.update-mobile-location-text-link {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.text-black {
|
||||
color: $black;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ import { getMountOptions } from "@/helpers/unit-test-helper";
|
|||
|
||||
import store from "@/store";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import { getServiceabilityDetails } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
import {
|
||||
getServiceabilityDetails,
|
||||
Provider,
|
||||
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
// Define Mocks
|
||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||
|
|
@ -16,6 +19,8 @@ jest.mock("@/helpers/cms-content-helper", () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
jest.mock("./classes/provider");
|
||||
|
||||
const mockGetPricedMobileFeePart = (mockServiceZipCode) => {
|
||||
let mobileFeePart = {};
|
||||
|
||||
|
|
@ -397,6 +402,7 @@ describe("service-location.vue", () => {
|
|||
|
||||
await wrapper.setData({
|
||||
selectedAppointmentType: "Mobile",
|
||||
providerData: { mobileProviderNumber: "01820" },
|
||||
});
|
||||
|
||||
const mobileLocationQuestionsComponent = wrapper.findComponent({
|
||||
|
|
@ -448,6 +454,7 @@ describe("service-location.vue", () => {
|
|||
|
||||
await wrapper.setData({
|
||||
selectedAppointmentType: "Mobile",
|
||||
providerData: { mobileProviderNumber: "01820" },
|
||||
});
|
||||
|
||||
const mobileLocationQuestionsComponent = wrapper.findComponent({
|
||||
|
|
@ -511,6 +518,7 @@ describe("service-location.vue", () => {
|
|||
|
||||
await wrapper.setData({
|
||||
selectedAppointmentType: "Mobile",
|
||||
providerData: { mobileProviderNumber: "01820" },
|
||||
});
|
||||
|
||||
const mobileLocationQuestionsComponent = wrapper.findComponent({
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center appointment-type">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-6 col-xxl-4">
|
||||
<appointmentTypeQuestion
|
||||
v-model="selectedAppointmentType"
|
||||
v-show="isAppointmentTypeDisplayed"
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<mobileLocationModalQuestions
|
||||
customComponentId="mobileLocationQuestions"
|
||||
v-if="selectedAppointmentType === 'Mobile'"
|
||||
|
|
@ -95,6 +95,7 @@
|
|||
v-model="selectedProvider"
|
||||
:selectedAppointmentType="selectedAppointmentType"
|
||||
:isDisplayed="isShopQuestionDisplayed"
|
||||
@updated-shop-list="setUpdatedShopList"
|
||||
cmsWidgetName="ShopQuestionWidget" />
|
||||
|
||||
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
|
|
@ -135,6 +136,9 @@ import {
|
|||
getPricedMobileFeePart,
|
||||
getServiceabilityDetails,
|
||||
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||
|
||||
import store from "@/store";
|
||||
|
||||
// Validation
|
||||
|
|
@ -176,6 +180,7 @@ export default {
|
|||
mobileFeePart: null,
|
||||
zipContainsMilitaryBase: false,
|
||||
zipCodeCtu: null,
|
||||
providerData: null,
|
||||
};
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
|
|
@ -229,7 +234,12 @@ export default {
|
|||
resultMap.serviceabilityDetails,
|
||||
resultMap.mobileFeePart
|
||||
);
|
||||
|
||||
// Initialize the Shop Question component
|
||||
vm.$refs.shopQuestion.initializeComponent(resultMap.shopQuestionInitialData);
|
||||
|
||||
// Initialize the page level shop data
|
||||
vm.providerData = resultMap.shopQuestionInitialData;
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -333,6 +343,9 @@ export default {
|
|||
displayNoShopsAlert() {
|
||||
return !this.isServiceableInshop && !this.isServiceableMobile;
|
||||
},
|
||||
recalibrationInformationModal() {
|
||||
return this.$refs.recalibrationInformationModal;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
|
|
@ -403,6 +416,15 @@ export default {
|
|||
this.isRecalibrationServiceableMobile =
|
||||
serviceabilityDetails.isRecalibrationServiceableMobile;
|
||||
},
|
||||
async reloadShopData(zipCode) {
|
||||
await this.$refs.shopQuestion.reloadShopData(zipCode);
|
||||
},
|
||||
setUpdatedShopList(providerData) {
|
||||
this.providerData = providerData;
|
||||
},
|
||||
openRecalibrationInformationModal() {
|
||||
this.recalibrationInformationModal.openModal();
|
||||
},
|
||||
backButtonAction() {
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
},
|
||||
|
|
@ -472,11 +494,25 @@ export default {
|
|||
this.updateAndSaveSupportingItems();
|
||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||
},
|
||||
openModalAction(modalName) {
|
||||
this.$refs[modalName].openModal();
|
||||
},
|
||||
watch: {
|
||||
selectedAppointmentType: {
|
||||
handler(newValue) {
|
||||
if (newValue === "Mobile") {
|
||||
this.selectedProvider = new Provider(this.providerData.mobileProviderNumber);
|
||||
} else {
|
||||
this.selectedProvider = new Provider();
|
||||
}
|
||||
},
|
||||
},
|
||||
async reloadShopData() {
|
||||
await this.$refs.shopQuestion.reloadShopData(this.zipCode);
|
||||
providerData: {
|
||||
handler(newValue) {
|
||||
if (newValue === "Mobile") {
|
||||
this.selectedProvider = new Provider(this.providerData.mobileProviderNumber);
|
||||
} else {
|
||||
this.selectedProvider = new Provider();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
@ -496,14 +532,6 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.appointment-type {
|
||||
@include media-breakpoint-up(md) {
|
||||
.col {
|
||||
flex: 0 0 auto;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.question-text {
|
||||
& > span {
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -54,6 +54,8 @@ import { nextTick } from "vue";
|
|||
|
||||
import { getAvailabilityRating } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
|
||||
|
||||
import { Provider } from "@/layouts/service-location/classes/provider";
|
||||
|
||||
defineRule("option-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
||||
export default {
|
||||
|
|
@ -136,6 +138,7 @@ export default {
|
|||
},
|
||||
initializeComponent(shopQuestionInitialData) {
|
||||
this.shopProviders = shopQuestionInitialData.shopProviders;
|
||||
this.$emit("updated-shop-list", shopQuestionInitialData);
|
||||
},
|
||||
async getNextShopsFromList(numberToGet = 3) {
|
||||
const shopIterator = (array, n) => {
|
||||
|
|
@ -206,9 +209,9 @@ export default {
|
|||
await this.getNextShopsFromList();
|
||||
},
|
||||
getSelectedProviderObject(providerNumber) {
|
||||
const provider = this.shopProviders?.find(
|
||||
(provider) => provider.providerNumber == providerNumber
|
||||
);
|
||||
const provider =
|
||||
this.shopProviders?.find((provider) => provider.providerNumber == providerNumber) ??
|
||||
new Provider();
|
||||
|
||||
return provider;
|
||||
},
|
||||
|
|
@ -222,18 +225,15 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
selectedAppointmentType: {
|
||||
async handler(newValue) {
|
||||
// Question: Does it matter if this fires when clicking Mobile? Ideally it should not, but it doesn't seem to effect anything
|
||||
// What we DON'T want to do here is create a dependency by checking if the selectedAppointmentType is Mobile as that has
|
||||
// nothing to do with this component. We could mitigate this by showing / hiding this component with v-if but that messes
|
||||
// up the component initialization on page load.
|
||||
async handler() {
|
||||
this.resetAnswers();
|
||||
|
||||
await nextTick();
|
||||
|
||||
this.selectedProviderNumber = null;
|
||||
|
||||
await nextTick();
|
||||
|
||||
if (newValue !== "Mobile") {
|
||||
this.getNextShopsFromList();
|
||||
}
|
||||
this.getNextShopsFromList();
|
||||
},
|
||||
},
|
||||
shopProviders: {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="container-fluid page-container-grouped-styles vehicle-damage">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="col-md-8 col-xl-6">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
cmsWidgetName="DamageLocationQuestion"
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
<div class="vehicle-parts small-question-text">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -28,6 +28,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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" />
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<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">
|
||||
<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"
|
||||
|
|
@ -48,7 +48,6 @@
|
|||
validationRules="style-required"
|
||||
placeHolderText="Select style"
|
||||
inputId="styleQuestionField" />
|
||||
<div class="col-md-3"> </div>
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="displayGeneric"
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<div class="container-fluid page-container-grouped-styles">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<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">
|
||||
<div class="col-md-6 col-xl-4 col-xxl-3">
|
||||
<vehicleBanner
|
||||
cmsWidgetName="VehicleBannerWidget"
|
||||
:displayGenericVehicleImage="false" />
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4 mt-5"
|
||||
cmsWidgetName="VinNumberQuestionWidget"
|
||||
v-model="vin"
|
||||
customInputId="vin"
|
||||
|
|
@ -37,9 +38,10 @@
|
|||
v-if="displayVinScanFailedAlert"
|
||||
alertClass="alert-danger" />
|
||||
|
||||
<vinInformation />
|
||||
<vinInformation class="mb-4" />
|
||||
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="ServiceZipQuestionWidget"
|
||||
v-model="serviceZipCode"
|
||||
customInputId="serviceZipCode"
|
||||
|
|
|
|||
|
|
@ -65,9 +65,6 @@ export default {
|
|||
},
|
||||
async getZipCodeData(zipCode, pageNameToLog = null) {
|
||||
const pageName = pageNameToLog ?? this.$options?.name;
|
||||
if (pageName == null) {
|
||||
console.log("Missed a spot.");
|
||||
}
|
||||
|
||||
const serviceZipValidationResponse = await this.dispatchStoreActionWithLogging(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
|
|
@ -91,7 +88,6 @@ export default {
|
|||
lineItem.partType != partTypeStrings.RAIN_DEFENSE
|
||||
);
|
||||
});
|
||||
console.log("step one", lineItemsToPrice);
|
||||
let totalPrice = this.getTotalPriceOfAllLineItemsAndChildParts(lineItemsToPrice);
|
||||
return totalPrice;
|
||||
},
|
||||
|
|
@ -113,19 +109,31 @@ export default {
|
|||
lineItem.childParts
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
"step two, lineItem name: ",
|
||||
lineItem.partNumber,
|
||||
" / totalPrice: ",
|
||||
totalPrice
|
||||
);
|
||||
});
|
||||
console.log("step three", totalPrice);
|
||||
return totalPrice;
|
||||
},
|
||||
getTotalLineItemPrice(lineItem) {
|
||||
return lineItem.kitPrice + lineItem.laborAmount + lineItem.sellingPrice;
|
||||
},
|
||||
getDisplayAmountDue(lineItems) {
|
||||
return this.getAmountDue(lineItems).toLocaleString("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
});
|
||||
},
|
||||
getAmountDue(lineItems) {
|
||||
// TODO: verify tax is included in lineitems
|
||||
// Price everything in lineitems except the server data string
|
||||
var amountDue = 0;
|
||||
const itemsClone = { ...lineItems };
|
||||
delete itemsClone.serverData;
|
||||
for (var propertyName in itemsClone) {
|
||||
amountDue += this.getTotalPriceOfAllLineItemsAndChildParts(
|
||||
itemsClone[propertyName]
|
||||
);
|
||||
}
|
||||
return amountDue;
|
||||
},
|
||||
scrollToPageTop() {
|
||||
const container = document.getElementsByClassName("page-container-grouped-styles")[0];
|
||||
container.scrollTo({ top: 0, left: 0, behavior: "smooth" });
|
||||
|
|
|
|||
|
|
@ -29,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",
|
||||
|
|
@ -60,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);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const externalUrls = {
|
||||
HERITAGE_FUNNEL: process.env.VUE_APP_HERITAGE_FUNNEL,
|
||||
SAFELITE_HOP: process.env.VUE_APP_SAFELITE_HOP,
|
||||
};
|
||||
|
||||
export { externalUrls };
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ const fmgPageValues = {
|
|||
CUSTOMER_DETAILS: "customer-details",
|
||||
REVIEW: "review",
|
||||
PAYMENT_METHOD: "payment-method",
|
||||
PAYMENT: "payment",
|
||||
PAYMENT_PIA_RETURN: "payment-pia-return",
|
||||
CONFIRMATION: "confirmation",
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,15 +465,53 @@ 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,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ const getDefaultState = () => {
|
|||
coverageStatus: null,
|
||||
},
|
||||
parentAccountNumber: 0,
|
||||
payNowType: "cc",
|
||||
piaErrorCode: null,
|
||||
},
|
||||
schedule: {
|
||||
date: null,
|
||||
|
|
@ -106,6 +108,8 @@ const getDefaultState = () => {
|
|||
referralDate: null,
|
||||
referralCorrelationId: null,
|
||||
eon: null,
|
||||
workOrderNumber: null,
|
||||
createDeleteStatusWorkOrderForPia: false,
|
||||
},
|
||||
applicationUser: {
|
||||
eventBus: [],
|
||||
|
|
@ -214,6 +218,18 @@ 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;
|
||||
},
|
||||
updatePiaWorkOrder(state, piaWorkOrder) {
|
||||
state.order.createDeleteStatusWorkOrderForPia = piaWorkOrder;
|
||||
},
|
||||
updateWorkOrderNumber(state, workOrderNumber) {
|
||||
state.order.workOrderNumber = workOrderNumber;
|
||||
},
|
||||
updateInsuranceVerifiedStatus(state, isVerified) {
|
||||
state.order.payment.insuranceCoverage.isVerified = isVerified;
|
||||
},
|
||||
|
|
@ -504,6 +520,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(
|
||||
|
|
@ -955,6 +972,7 @@ export const actions = {
|
|||
parentAccountNumber,
|
||||
savedSessionId,
|
||||
crmCustomerId,
|
||||
workOrderNumber,
|
||||
}
|
||||
) {
|
||||
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||
|
|
@ -965,6 +983,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(
|
||||
|
|
@ -1369,6 +1388,7 @@ export const actions = {
|
|||
? convertGlassPieceToBackEndCompatibleFormat(order.damage.glassToReplace)
|
||||
: [];
|
||||
var payload = {
|
||||
zipCode: order.serviceLocation.provider.zipCode,
|
||||
providerNumber: providerNumber,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
|
|
@ -1492,6 +1512,14 @@ export const actions = {
|
|||
});
|
||||
},
|
||||
|
||||
// Signing Service for Payment
|
||||
getSignature() {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetSignature.method,
|
||||
endpoint: endpoints.GetSignature.url,
|
||||
});
|
||||
},
|
||||
|
||||
// Session API Actions
|
||||
saveSession(context, { pageNameToLog }) {
|
||||
const vehicle = context.getters.vehicle;
|
||||
|
|
@ -1597,6 +1625,7 @@ export const actions = {
|
|||
referralNumber: order.referralNumber?.toString(),
|
||||
referralSequenceNumber: order.referralSequenceNumber,
|
||||
eon: order.eon,
|
||||
createDeleteStatusWorkOrderForPia: order.createDeleteStatusWorkOrderForPia,
|
||||
},
|
||||
},
|
||||
logApiCall: true,
|
||||
|
|
@ -1670,6 +1699,12 @@ 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);
|
||||
},
|
||||
savePiaWorkOrder(context, piaWorkOrder) {
|
||||
context.commit(storeMutations.UPDATE_PIA_WORK_ORDER, piaWorkOrder);
|
||||
},
|
||||
|
||||
// Business domain actions
|
||||
|
||||
|
|
@ -1959,6 +1994,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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2767,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,8 +5,6 @@ body {
|
|||
background-color: #fff;
|
||||
color: #4d5151;
|
||||
.container-fluid {
|
||||
max-width: 1400px;
|
||||
padding: 0 1.5rem;
|
||||
.prevent-squish {
|
||||
overflow-x: unset;
|
||||
}
|
||||
|
|
@ -42,9 +40,11 @@ body {
|
|||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
//Disable scroll of main container when modal is open
|
||||
.modal-open {
|
||||
.page-container-grouped-styles {
|
||||
overflow: hidden;
|
||||
.container-fluid {
|
||||
&.page-container-grouped-styles {
|
||||
overflow: hidden;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,36 +1,38 @@
|
|||
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";
|
||||
|
||||
// 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,31 @@ 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__";
|
||||
|
||||
// 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