Merge pull request #360 from Safelite/develop

Re-aligning main branch with what we have in develop
This commit is contained in:
Jeremy-Z 2023-07-07 11:44:32 -04:00 committed by GitHub
commit afe2af039d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
291 changed files with 52464 additions and 2 deletions

4
.browserslistrc Normal file
View file

@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

21
.eslintrc.js Normal file
View file

@ -0,0 +1,21 @@
module.exports = {
extends: [
'eslint-config-airbnb-base',
'plugin:vue/vue3-strongly-recommended'
],
rules: {
'linebreak-style': 'off',
'vue/component-definition-name-casing': ['warn', 'kebab-case'],
'vue/require-default-prop': 'off',
'vue/attribute-hyphenation': ['warn', 'never'],
'vue/v-on-event-hyphenation': ['warn', 'never']
},
settings: {
'import/resolver': {
alias: {
map: [['@', './src/']],
extensions: ['.js', '.vue']
}
}
}
};

28
.gitignore vendored Normal file
View file

@ -0,0 +1,28 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Misc
coverage/*
junit.xml
/.vs

View file

@ -1,2 +1,24 @@
# DigitalConsumer.ISS
Digital Consumer Insurance Self Service (ISS) rewrite
# digitalconsumer.iss
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your unit tests
```
npm run test:unit
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

239
azure-pipelines.yml Normal file
View file

@ -0,0 +1,239 @@
trigger:
branches:
include: [ develop, release/* ]
paths:
exclude:
- deployment/*
include:
- "*"
pr:
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
repositories:
- repository: AzureDevOps
type: github
name: Safelite/AzureDevOps
endpoint: Safelite
ref: refs/tags/t5.5.40
variables:
- group: Digital-Infrastructure
- group: ISS-BuildBranches
stages:
# PR's
- ${{ if eq(variables['Build.Reason'], 'PullRequest') }}:
- 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: ISS-Dev
jobs:
- deployment: devBuildDeployment
displayName: Build and Deploy ISS - 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)
deployBuckets:
safelite-dev-iss-us-east-2:
clearFolder: true
deployFolder: ''
region: us-east-2
safelite-dev-iss-us-east-1:
clearFolder: true
deployFolder: ''
region: us-east-1
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
cfDistributionId: $(cfDistributionId)
# Test Build/Deploy
- stage: Test
condition: eq(variables['Build.SourceBranch'], variables['test-branch'] )
variables:
- group: ISS-Test
jobs:
- deployment: testBuildDeployment
displayName: Build and Deploy ISS - 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)
deployBuckets:
safelite-sys-iss-us-east-1:
clearFolder: true
deployFolder: ""
region: us-east-1
safelite-sys-iss-us-east-2:
clearFolder: true
deployFolder: ""
region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
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__)
# QA Build/Deploy
- stage: QA
condition: eq(variables['Build.SourceBranch'], variables['qa-branch'] )
variables:
- group: ISS-QA
jobs:
- deployment: qaBuildDeployment
displayName: Build and Deploy ISS - 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)
deployBuckets:
safelite-qa-iss-us-east-1:
clearFolder: true
deployFolder: ""
region: us-east-1
safelite-qa-iss-us-east-2:
clearFolder: true
deployFolder: ""
region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
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: $(cfDistributionId)
paths: /*
awsProfile: $(qaDeploymentProfile)
# Prod Build/Deploy
- stage: Prod
condition: succeeded('Qa')
variables:
- group: ISS-Prod
jobs:
- deployment: prodBuildDeployment
displayName: Build and Deploy ISS - 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)
deployBuckets:
safelite-prod-iss-us-east-1:
clearFolder: true
deployFolder: ""
region: us-east-1
safelite-prod-iss-us-east-2:
clearFolder: true
deployFolder: ""
region: us-east-2
appDeployVariables:
__VUE_APP_CONSUMER_CF_DISTRO__: $(__VUE_APP_CONSUMER_CF_DISTRO__)
__VUE_APP_CURRENT_ENVIRONMENT__: $(__VUE_APP_CURRENT_ENVIRONMENT__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
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: $(cfDistributionId)
paths: /*
awsProfile: $(prodDeploymentProfile)
- template: templates/digital/auto-tag.yml@AzureDevOps
parameters:
dependsOn: prodBuildDeployment
userName: SafeliteAzureDevops
userEmail: githubazuredevops@safelite.com

3
babel.config.js Normal file
View file

@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};

23
jest.config.js Normal file
View file

@ -0,0 +1,23 @@
module.exports = {
verbose: true,
coverageReporters: ["html", "text", "cobertura"],
reporters: ["default", "jest-junit"],
testResultsProcessor: "jest-junit",
preset: "@vue/cli-plugin-unit-jest",
transform: { "^.+\\.vue$": "@vue/vue3-jest" },
moduleFileExtensions: ["js", "vue"],
collectCoverageFrom: [
"src/**/*.{js,vue}",
"!src/main.js",
"!src/constants/*.js",
"!src/router/**/*.js",
"!src/helpers/unit-test-helper.js"
// END
], // ! means exclude from coverage.
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: {
// global: {
// statements: 80,
// },
},
};

17088
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

45
package.json Normal file
View file

@ -0,0 +1,45 @@
{
"name": "digitalconsumer.iss",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit --coverage --ci",
"test:unit:lite": "vue-cli-service test:unit --ci"
},
"dependencies": {
"@pinia/testing": "0.0.14",
"@popperjs/core": "^2.10.2",
"axios": "^0.27.2",
"bootstrap": "^5.2.2",
"jest-junit": "^13.0.0",
"maska": "^1.5.0",
"pinia": "^2.0.22",
"pinia-plugin-persistedstate": "^2.2.0",
"vee-validate": "^4.7.0",
"vue": "^3.2.13",
"vue-plugin-load-script": "^2.1.0",
"vue-router": "4.1.3"
},
"devDependencies": {
"@testing-library/jest-dom": "5.16.5",
"@testing-library/user-event": "14.4.3",
"@testing-library/vue": "6.6.1",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-unit-jest": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/test-utils": "^2.0.0-0",
"@vue/vue3-jest": "^27.0.0-alpha.1",
"babel-jest": "^27.0.6",
"eslint": "8.29.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-import-resolver-alias": "1.1.2",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-vue": "9.8.0",
"jest": "^27.0.5",
"sass": "^1.32.7",
"sass-loader": "^12.0.0"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

31
public/index.html Normal file
View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<script>
window.dataLayer = [{}];
</script>
<script><%= process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY %></script>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap" rel="stylesheet">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- Google Tag Manager -->
<noscript>
<iframe src="<%= process.env.VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC %>"
height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

18
src/App.vue Normal file
View file

@ -0,0 +1,18 @@
<template>
<router-view v-slot="{ Component }">
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in">
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
<component :is="Component" />
</transition>
</router-view>
</template>
<style lang="scss">
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss";
@import "@/styles/common-typography-styles.scss";
@import "@/styles/common-error-styles.scss";
@import "@/styles/common-animations.scss";
@import "@/styles/shared-input-button-styles.scss";
@import "@/styles/client-customizations.scss";
</style>

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 30 14" viewBox="0 0 30 14">
<path d="M7.4 12.8h6.8l3.1-11.6H7.4C4.2 1.2 1.6 3.8 1.6 7s2.6 5.8 5.8 5.8z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#fff"/>
<path d="M22.6 0H7.4c-3.9 0-7 3.1-7 7s3.1 7 7 7h15.2c3.9 0 7-3.1 7-7s-3.2-7-7-7zm-21 7c0-3.2 2.6-5.8 5.8-5.8h9.9l-3.1 11.6H7.4c-3.2 0-5.8-2.6-5.8-5.8z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#06f"/>
<path d="M24.6 4c.2.2.2.6 0 .8L22.5 7l2.2 2.2c.2.2.2.6 0 .8-.2.2-.6.2-.8 0l-2.2-2.2-2.2 2.2c-.2.2-.6.2-.8 0-.2-.2-.2-.6 0-.8L20.8 7l-2.2-2.2c-.2-.2-.2-.6 0-.8.2-.2.6-.2.8 0l2.2 2.2L23.8 4c.2-.2.6-.2.8 0z" style="fill:#fff"/>
<path d="M12.7 4.1c.2.2.3.6.1.8L8.6 9.8c-.1.1-.2.2-.3.2-.2.1-.5.1-.7-.1L5.4 7.7c-.2-.2-.2-.6 0-.8.2-.2.6-.2.8 0L8 8.6l3.8-4.5c.2-.2.6-.2.9 0z" style="fill:#06f"/>
</svg>

After

Width:  |  Height:  |  Size: 865 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12.8 16" xml:space="preserve">
<path d="M6.4 0c-.85 0-1.69.18-2.47.52-.78.34-1.49.84-2.07 1.47A7.026 7.026 0 0 0 0 6.75c0 1.77.67 3.48 1.86 4.77l4.02 4.26c.07.07.15.13.23.16.1.04.19.06.29.06.1 0 .19-.02.28-.06.09-.04.17-.09.23-.16l4.02-4.26a6.884 6.884 0 0 0 1.87-4.77c.01-1.78-.66-3.49-1.87-4.77A6.166 6.166 0 0 0 8.86.51C8.09.18 7.25 0 6.4 0zm0 9.13c-.47 0-.93-.14-1.32-.41-.39-.27-.7-.64-.88-1.09-.19-.44-.23-.93-.14-1.4.09-.47.32-.9.65-1.24.33-.34.76-.57 1.22-.66.46-.09.94-.05 1.38.14.44.18.81.49 1.07.89s.4.87.4 1.35c0 .64-.25 1.26-.7 1.71-.44.46-1.05.71-1.68.71z" fill="#8e9292"/>
</svg>

After

Width:  |  Height:  |  Size: 650 B

View file

@ -0,0 +1,11 @@
<svg viewBox="0 7 78 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.83691 15.1348L8.94207 14.6427C9.08672 13.9643 8.77363 13.3983 7.85528 13.3983C6.83511 13.3983 6.22273 14.117 6.11233 14.6337C5.62652 16.9121 13.6185 15.7976 12.7192 20.014C12.1953 22.4697 9.69054 23.5686 5.50708 23.5686C1.57914 23.5686 -0.431696 22.7119 0.0779134 20.3212L0.164038 19.9168H4.75576L4.6506 20.4094C4.44028 21.3945 5.00223 21.75 5.92058 21.75C6.88983 21.75 7.60072 21.0484 7.73537 20.4179C8.22119 18.14 0.531851 19.2706 1.40356 15.1832C1.89985 12.8569 4.13671 11.5802 8.14125 11.5802C12.2733 11.5802 13.8026 12.663 13.2754 15.1348H8.83691Z" fill="#DA291C"/>
<path d="M18.8241 19.8334C18.1608 20.0003 17.8315 20.3473 17.7154 20.8906C17.5993 21.4353 17.8843 21.9999 18.5286 21.9999C19.4831 21.9999 19.9618 21.5472 20.1521 20.6555L20.3843 19.5651C20.3843 19.5651 19.4869 19.6665 18.8241 19.8334ZM20.8763 17.2127C20.9829 16.714 20.6893 16.4571 19.9499 16.4571C19.1386 16.4571 18.6623 16.9013 18.4544 17.7627H14.732C15.2321 15.4203 18.3016 15.0121 20.2582 15.0121C23.2645 15.0121 25.2582 15.5108 24.8033 17.6418L23.9521 21.6316C23.7908 22.3873 23.7127 22.977 23.9806 23.3999H19.8286C19.7467 23.1131 19.7639 22.8101 19.7629 22.4778H19.7153C19.0834 23.2036 17.851 23.6123 16.5625 23.6123C13.9854 23.6123 13.196 22.6139 13.4958 21.2082C13.8184 19.6974 14.8048 18.9872 17.636 18.579L19.3851 18.3221C20.2069 18.2054 20.4619 18.186 20.677 18.1481L20.8763 17.2127Z" fill="#DA291C"/>
<path d="M27.0505 17.0521H25.5711L25.9613 15.2236H27.4406L27.7109 13.9554C28.0592 12.3232 29.1902 11.7188 32.1732 11.7188C32.7932 11.7188 33.5507 11.7491 34.2059 11.8093L33.8881 13.2998C33.6797 13.2694 33.4917 13.2543 33.182 13.2543C32.6566 13.2543 32.1204 13.4202 32.0333 13.8289L31.7354 15.2236H33.4774L33.0873 17.0521H31.3453L29.9911 23.3999H25.6958L27.0505 17.0521Z" fill="#DA291C"/>
<path d="M39.793 17.7112C39.9767 16.8498 39.7744 16.4573 38.8675 16.4573C37.9848 16.4573 37.6147 16.8498 37.431 17.7112L37.3068 18.292H39.6693L39.793 17.7112ZM36.9908 19.7734L36.7158 21.0629C36.5288 21.94 37.0532 22.1666 37.6494 22.1666C38.4131 22.1666 38.9774 21.6475 39.2158 20.5286H43.0814C42.6113 22.5084 40.9207 23.612 37.3648 23.612C33.2133 23.612 32.1284 22.4325 32.7926 19.3198C33.4602 16.1909 35.334 15.0118 39.1996 15.0118C42.6356 15.0118 44.1715 15.9794 43.5396 18.9419L43.3621 19.7734H36.9908Z" fill="#DA291C"/>
<path d="M46.6878 11.3427H50.9831L48.4108 23.4002H44.1155L46.6878 11.3427Z" fill="#DA291C"/>
<path d="M52.8394 11.3427H57.1342L56.6098 13.8026H52.3141L52.8394 11.3427ZM52.0114 15.2234H56.3062L54.5628 23.3974H50.268L52.0114 15.2234Z" fill="#DA291C"/>
<path d="M57.2803 15.2234H58.712L58.9338 14.1814L63.9361 10.7557L63.0068 15.2234H64.7013L64.3106 17.0519H62.6167L61.7383 21.1692C61.6384 21.6381 61.9372 21.8036 62.629 21.8036H63.2971L62.9569 23.4002C62.1561 23.46 61.3143 23.4908 60.5992 23.4908C57.974 23.4908 57.0062 23.2187 57.3931 21.4049L58.3214 17.0519H56.8901L57.2803 15.2234Z" fill="#DA291C"/>
<path d="M71.351 17.7112C71.5347 16.8498 71.332 16.4573 70.4255 16.4573C69.5424 16.4573 69.1722 16.8498 68.9885 17.7112L68.8648 18.292H71.2268L71.351 17.7112ZM68.5484 19.7734L68.2734 21.0629C68.0869 21.94 68.6107 22.1666 69.2074 22.1666C69.9711 22.1666 70.535 21.6475 70.7734 20.5286H74.639C74.1693 22.5084 72.4783 23.612 68.9234 23.612C64.7708 23.612 63.6864 22.4325 64.3507 19.3198C65.0183 16.1909 66.8921 15.0118 70.7577 15.0118C74.1941 15.0118 75.7291 15.9794 75.0972 18.9419L74.9197 19.7734H68.5484Z" fill="#DA291C"/>
<path d="M76.2645 23.0422C76.4482 23.0365 76.4905 22.9606 76.4905 22.8648C76.4905 22.7719 76.4482 22.7245 76.293 22.7245H76.058V23.0422H76.2645ZM76.058 23.236V23.7741H75.8634V22.5302H76.2673C76.4957 22.5302 76.7332 22.5923 76.7332 22.8729C76.7332 23.0166 76.6461 23.1299 76.4791 23.1635V23.1687C76.6518 23.2024 76.6713 23.2782 76.6908 23.4138C76.708 23.5314 76.7222 23.6618 76.7646 23.7741H76.5157C76.5019 23.7035 76.4819 23.6248 76.4734 23.5518C76.4591 23.4446 76.4591 23.3465 76.4029 23.2872C76.3544 23.236 76.2902 23.2422 76.2221 23.236H76.058ZM77.1571 23.1664C77.1571 22.6875 76.7674 22.2969 76.284 22.2969C75.7987 22.2969 75.4056 22.6875 75.4056 23.1664C75.4056 23.6504 75.7987 24.041 76.284 24.041C76.7674 24.041 77.1571 23.6504 77.1571 23.1664ZM75.2196 23.1664C75.2196 22.5832 75.694 22.1106 76.284 22.1106C76.8716 22.1106 77.3437 22.5832 77.3437 23.1664C77.3437 23.7542 76.8716 24.2269 76.284 24.2269C75.694 24.2269 75.2196 23.7542 75.2196 23.1664Z" fill="#DA291C"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" xml:space="preserve">
<path d="M24.01 48c-2.93 0-5.92-.52-8.85-1.58-6.25-2.25-11.33-7.33-13.58-13.57C-1.25 25-.25 16.76 4.34 10.22A24.05 24.05 0 0 1 24 0v2.26c-7.08 0-13.74 3.46-17.81 9.27-4.16 5.91-5.07 13.4-2.49 20.55 2.03 5.62 6.6 10.19 12.22 12.22 7.14 2.58 14.64 1.67 20.56-2.49A21.78 21.78 0 0 0 45.74 24H48c0 7.81-3.82 15.16-10.22 19.66A23.846 23.846 0 0 1 24.01 48z"/>
</svg>

After

Width:  |  Height:  |  Size: 446 B

BIN
src/assets/img/loader.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8976)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8976" x1="51.9586" y1="33.0102" x2="51.9586" y2="6.98049" gradientUnits="userSpaceOnUse">
<stop stop-color="#CADCDD"/>
<stop offset="0.2653" stop-color="#C8DBDC"/>
<stop offset="0.3972" stop-color="#C0D6D7"/>
<stop offset="0.4996" stop-color="#B3CDD0"/>
<stop offset="0.5869" stop-color="#A0C1C5"/>
<stop offset="0.6645" stop-color="#88B1B8"/>
<stop offset="0.7353" stop-color="#6A9EA7"/>
<stop offset="0.8009" stop-color="#468793"/>
<stop offset="0.8607" stop-color="#1E6D7C"/>
<stop offset="0.8993" stop-color="#005A6B"/>
<stop offset="1" stop-color="#005A6B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_9006)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_9006" x1="51.9586" y1="33.0065" x2="51.9586" y2="6.97682" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0CDC2"/>
<stop offset="0.2632" stop-color="#CECCC1"/>
<stop offset="0.394" stop-color="#C6C8BE"/>
<stop offset="0.4956" stop-color="#B9C0B8"/>
<stop offset="0.5823" stop-color="#A6B6B1"/>
<stop offset="0.6593" stop-color="#8EA8A6"/>
<stop offset="0.7295" stop-color="#70989A"/>
<stop offset="0.7947" stop-color="#4C848B"/>
<stop offset="0.854" stop-color="#246E7A"/>
<stop offset="0.8993" stop-color="#005A6B"/>
<stop offset="1" stop-color="#005A6B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,19 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8763)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8763" x1="51.9586" y1="33.0065" x2="51.9586" y2="6.97682" gradientUnits="userSpaceOnUse">
<stop stop-color="#E7E7E8"/>
<stop offset="0.2559" stop-color="#E5E6E7"/>
<stop offset="0.383" stop-color="#DDE1E3"/>
<stop offset="0.4818" stop-color="#D0D9DC"/>
<stop offset="0.5659" stop-color="#BDCDD1"/>
<stop offset="0.6409" stop-color="#A5BFC4"/>
<stop offset="0.7091" stop-color="#87ACB4"/>
<stop offset="0.7724" stop-color="#6397A1"/>
<stop offset="0.8318" stop-color="#3A7D8A"/>
<stop offset="0.8859" stop-color="#0C6272"/>
<stop offset="0.8993" stop-color="#005A6B"/>
<stop offset="1" stop-color="#005A6B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8750)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8750" x1="51.9586" y1="33.0219" x2="51.9586" y2="6.99218" gradientUnits="userSpaceOnUse">
<stop stop-color="#CBE9DD"/>
<stop offset="0.265" stop-color="#C9E7DC"/>
<stop offset="0.3966" stop-color="#C1E2D7"/>
<stop offset="0.4989" stop-color="#B4D9D0"/>
<stop offset="0.5861" stop-color="#A1CCC6"/>
<stop offset="0.6636" stop-color="#89BAB8"/>
<stop offset="0.7343" stop-color="#6BA5A7"/>
<stop offset="0.7998" stop-color="#478C93"/>
<stop offset="0.8595" stop-color="#1F707D"/>
<stop offset="0.8993" stop-color="#005A6B"/>
<stop offset="1" stop-color="#005A6B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,19 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8908)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8908" x1="51.9812" y1="33.0046" x2="51.9812" y2="6.97492" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="0.2491" stop-color="#FDFEFE"/>
<stop offset="0.3729" stop-color="#F5F9F9"/>
<stop offset="0.4691" stop-color="#E8F0F2"/>
<stop offset="0.551" stop-color="#D5E4E7"/>
<stop offset="0.6239" stop-color="#BDD4D9"/>
<stop offset="0.6904" stop-color="#9FC1C7"/>
<stop offset="0.752" stop-color="#7BAAB3"/>
<stop offset="0.8099" stop-color="#528F9A"/>
<stop offset="0.8626" stop-color="#247280"/>
<stop offset="0.8993" stop-color="#005A6B"/>
<stop offset="1" stop-color="#005A6B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8984)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8984" x1="51.9519" y1="33.0065" x2="51.9519" y2="6.97682" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0CDC2"/>
<stop offset="0.2738" stop-color="#CECBC0"/>
<stop offset="0.4097" stop-color="#C9C6B8"/>
<stop offset="0.5154" stop-color="#C0BCAB"/>
<stop offset="0.6055" stop-color="#B3AD98"/>
<stop offset="0.6856" stop-color="#A39B80"/>
<stop offset="0.7586" stop-color="#8E8462"/>
<stop offset="0.8262" stop-color="#766A3E"/>
<stop offset="0.8879" stop-color="#5B4B16"/>
<stop offset="0.8993" stop-color="#55450E"/>
<stop offset="1" stop-color="#55450E"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,17 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8980)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8980" x1="51.9552" y1="33.0102" x2="51.9552" y2="6.98049" gradientUnits="userSpaceOnUse">
<stop stop-color="#CADCDD"/>
<stop offset="0.2908" stop-color="#C8DADB"/>
<stop offset="0.4353" stop-color="#C1D2D3"/>
<stop offset="0.5475" stop-color="#B6C5C6"/>
<stop offset="0.6432" stop-color="#A5B2B3"/>
<stop offset="0.7282" stop-color="#909A9B"/>
<stop offset="0.8057" stop-color="#767C7D"/>
<stop offset="0.8761" stop-color="#58595A"/>
<stop offset="0.8993" stop-color="#4C4C4D"/>
<stop offset="1" stop-color="#4C4C4D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 981 B

View file

@ -0,0 +1,17 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_9018)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_9018" x1="51.9552" y1="33.0065" x2="51.9552" y2="6.97682" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0CDC2"/>
<stop offset="0.2978" stop-color="#CECBC0"/>
<stop offset="0.4457" stop-color="#C6C3B9"/>
<stop offset="0.5606" stop-color="#B9B6AE"/>
<stop offset="0.6585" stop-color="#A6A49D"/>
<stop offset="0.7456" stop-color="#8E8C87"/>
<stop offset="0.8237" stop-color="#71706D"/>
<stop offset="0.8959" stop-color="#4E4E4F"/>
<stop offset="0.8993" stop-color="#4C4C4D"/>
<stop offset="1" stop-color="#4C4C4D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 981 B

View file

@ -0,0 +1,17 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8978)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8978" x1="51.9552" y1="33.0065" x2="51.9552" y2="6.97682" gradientUnits="userSpaceOnUse">
<stop stop-color="#E7E7E8"/>
<stop offset="0.2851" stop-color="#E5E5E6"/>
<stop offset="0.4267" stop-color="#DDDDDE"/>
<stop offset="0.5368" stop-color="#D0D0D1"/>
<stop offset="0.6305" stop-color="#BDBDBE"/>
<stop offset="0.7139" stop-color="#A5A5A6"/>
<stop offset="0.7899" stop-color="#878788"/>
<stop offset="0.8589" stop-color="#646465"/>
<stop offset="0.8993" stop-color="#4C4C4D"/>
<stop offset="1" stop-color="#4C4C4D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 981 B

View file

@ -0,0 +1,17 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8932)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8932" x1="51.9552" y1="33.0219" x2="51.9552" y2="6.99218" gradientUnits="userSpaceOnUse">
<stop stop-color="#CBE9DD"/>
<stop offset="0.2841" stop-color="#C9E7DB"/>
<stop offset="0.4252" stop-color="#C3DFD4"/>
<stop offset="0.5349" stop-color="#B8D2C8"/>
<stop offset="0.6283" stop-color="#A9BFB7"/>
<stop offset="0.7114" stop-color="#96A7A0"/>
<stop offset="0.7871" stop-color="#7D8985"/>
<stop offset="0.8559" stop-color="#616665"/>
<stop offset="0.8993" stop-color="#4C4C4D"/>
<stop offset="1" stop-color="#4C4C4D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 981 B

View file

@ -0,0 +1,20 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="#CEE7DB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8920)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8920" x1="51.9618" y1="33.0102" x2="51.9618" y2="6.98049" gradientUnits="userSpaceOnUse">
<stop stop-color="#CADCDD"/>
<stop offset="0.2653" stop-color="#C8DBDB"/>
<stop offset="0.3972" stop-color="#C0D6D5"/>
<stop offset="0.4996" stop-color="#B3CFCB"/>
<stop offset="0.5869" stop-color="#A0C4BD"/>
<stop offset="0.6645" stop-color="#88B6AA"/>
<stop offset="0.7353" stop-color="#6AA593"/>
<stop offset="0.8009" stop-color="#469177"/>
<stop offset="0.8607" stop-color="#1E7A58"/>
<stop offset="0.8993" stop-color="#006941"/>
<stop offset="1" stop-color="#006941"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8982)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8982" x1="51.9814" y1="33.0065" x2="51.9814" y2="6.97682" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0CDC2"/>
<stop offset="0.2632" stop-color="#CECCC1"/>
<stop offset="0.394" stop-color="#C6C8BC"/>
<stop offset="0.4956" stop-color="#B9C2B4"/>
<stop offset="0.5823" stop-color="#A6B9A8"/>
<stop offset="0.6593" stop-color="#8EAD99"/>
<stop offset="0.7295" stop-color="#709F86"/>
<stop offset="0.7947" stop-color="#4C8E70"/>
<stop offset="0.854" stop-color="#247A57"/>
<stop offset="0.8993" stop-color="#006941"/>
<stop offset="1" stop-color="#006941"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="url(#paint0_linear_841_8896)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8896" x1="51.9618" y1="33.0219" x2="51.9618" y2="6.99218" gradientUnits="userSpaceOnUse">
<stop stop-color="#CBE9DD"/>
<stop offset="0.265" stop-color="#C9E8DB"/>
<stop offset="0.3966" stop-color="#C1E3D5"/>
<stop offset="0.4989" stop-color="#B4DACB"/>
<stop offset="0.5861" stop-color="#A1CFBD"/>
<stop offset="0.6636" stop-color="#89BFAA"/>
<stop offset="0.7343" stop-color="#6BAC93"/>
<stop offset="0.7998" stop-color="#479678"/>
<stop offset="0.8595" stop-color="#1F7D59"/>
<stop offset="0.8993" stop-color="#006941"/>
<stop offset="1" stop-color="#006941"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="#CBDDDE" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="#D1CEC3" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="#5D564C" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="#D3D3D3" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="#E9E9EB" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="#CBE9DD" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="white" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 368 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.25 33H25.7083C24.2188 33 23 31.7812 23 30.2917V9.70833C23 8.21874 24.2188 7 25.7083 7H78.25C79.7396 7 80.9583 8.21874 80.9583 9.70833V30.2917C80.9583 31.7812 79.7396 33 78.25 33Z" fill="#626167" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_8966)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8966" x1="51.9716" y1="34.0522" x2="51.9716" y2="5.97896" gradientUnits="userSpaceOnUse">
<stop stop-color="#CADCDD"/>
<stop offset="0.2653" stop-color="#C8DBDC"/>
<stop offset="0.3972" stop-color="#C0D6D7"/>
<stop offset="0.4996" stop-color="#B3CDD0"/>
<stop offset="0.5869" stop-color="#A0C1C5"/>
<stop offset="0.6645" stop-color="#88B1B8"/>
<stop offset="0.7353" stop-color="#6A9EA7"/>
<stop offset="0.8009" stop-color="#468793"/>
<stop offset="0.8607" stop-color="#1E6D7C"/>
<stop offset="0.8993" stop-color="#005A6B"/>
<stop offset="1" stop-color="#005A6B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_9004)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_9004" x1="51.9716" y1="34.0483" x2="51.9716" y2="5.975" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0CDC2"/>
<stop offset="0.2632" stop-color="#CECCC1"/>
<stop offset="0.394" stop-color="#C6C8BE"/>
<stop offset="0.4956" stop-color="#B9C0B8"/>
<stop offset="0.5823" stop-color="#A6B6B1"/>
<stop offset="0.6593" stop-color="#8EA8A6"/>
<stop offset="0.7295" stop-color="#70989A"/>
<stop offset="0.7947" stop-color="#4C848B"/>
<stop offset="0.854" stop-color="#246E7A"/>
<stop offset="0.8993" stop-color="#005A6B"/>
<stop offset="1" stop-color="#005A6B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,19 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_825_8758)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_825_8758" x1="51.9716" y1="34.0483" x2="51.9716" y2="5.975" gradientUnits="userSpaceOnUse">
<stop stop-color="#E7E7E8"/>
<stop offset="0.2559" stop-color="#E5E6E7"/>
<stop offset="0.383" stop-color="#DDE1E3"/>
<stop offset="0.4818" stop-color="#D0D9DC"/>
<stop offset="0.5659" stop-color="#BDCDD1"/>
<stop offset="0.6409" stop-color="#A5BFC4"/>
<stop offset="0.7091" stop-color="#87ACB4"/>
<stop offset="0.7724" stop-color="#6397A1"/>
<stop offset="0.8318" stop-color="#3A7D8A"/>
<stop offset="0.8859" stop-color="#0C6272"/>
<stop offset="0.8993" stop-color="#005A6B"/>
<stop offset="1" stop-color="#005A6B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,17 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.99" fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_206_8118)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_206_8118" x1="51.9712" y1="34.0648" x2="51.9712" y2="5.99159" gradientUnits="userSpaceOnUse">
<stop stop-color="#CEDEE7"/>
<stop offset="0.265" stop-color="#C9E7DC"/>
<stop offset="0.3966" stop-color="#C1E2D7"/>
<stop offset="0.4989" stop-color="#B4D9D0"/>
<stop offset="0.5861" stop-color="#A1CCC6"/>
<stop offset="0.6636" stop-color="#89BAB8"/>
<stop offset="0.7343" stop-color="#6BA5A7"/>
<stop offset="0.7998" stop-color="#478C93"/>
<stop offset="0.8993" stop-color="#125B7E"/>
<stop offset="1" stop-color="#125B7E"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,19 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_8906)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8906" x1="52.0028" y1="34.0462" x2="52.0028" y2="5.97295" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="0.2491" stop-color="#FDFEFE"/>
<stop offset="0.3729" stop-color="#F5F9F9"/>
<stop offset="0.4691" stop-color="#E8F0F2"/>
<stop offset="0.551" stop-color="#D5E4E7"/>
<stop offset="0.6239" stop-color="#BDD4D9"/>
<stop offset="0.6904" stop-color="#9FC1C7"/>
<stop offset="0.752" stop-color="#7BAAB3"/>
<stop offset="0.8099" stop-color="#528F9A"/>
<stop offset="0.8626" stop-color="#247280"/>
<stop offset="0.8993" stop-color="#005A6B"/>
<stop offset="1" stop-color="#005A6B"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_8974)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8974" x1="51.9623" y1="34.0483" x2="51.9623" y2="5.975" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0CDC2"/>
<stop offset="0.2738" stop-color="#CECBC0"/>
<stop offset="0.4097" stop-color="#C9C6B8"/>
<stop offset="0.5154" stop-color="#C0BCAB"/>
<stop offset="0.6055" stop-color="#B3AD98"/>
<stop offset="0.6856" stop-color="#A39B80"/>
<stop offset="0.7586" stop-color="#8E8462"/>
<stop offset="0.8262" stop-color="#766A3E"/>
<stop offset="0.8879" stop-color="#5B4B16"/>
<stop offset="0.8993" stop-color="#55450E"/>
<stop offset="1" stop-color="#55450E"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,17 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_8970)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8970" x1="51.9669" y1="34.0522" x2="51.9669" y2="5.97896" gradientUnits="userSpaceOnUse">
<stop stop-color="#CADCDD"/>
<stop offset="0.2908" stop-color="#C8DADB"/>
<stop offset="0.4353" stop-color="#C1D2D3"/>
<stop offset="0.5475" stop-color="#B6C5C6"/>
<stop offset="0.6432" stop-color="#A5B2B3"/>
<stop offset="0.7282" stop-color="#909A9B"/>
<stop offset="0.8057" stop-color="#767C7D"/>
<stop offset="0.8761" stop-color="#58595A"/>
<stop offset="0.8993" stop-color="#4C4C4D"/>
<stop offset="1" stop-color="#4C4C4D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,17 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_9016)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_9016" x1="51.9669" y1="34.0483" x2="51.9669" y2="5.975" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0CDC2"/>
<stop offset="0.2978" stop-color="#CECBC0"/>
<stop offset="0.4457" stop-color="#C6C3B9"/>
<stop offset="0.5606" stop-color="#B9B6AE"/>
<stop offset="0.6585" stop-color="#A6A49D"/>
<stop offset="0.7456" stop-color="#8E8C87"/>
<stop offset="0.8237" stop-color="#71706D"/>
<stop offset="0.8959" stop-color="#4E4E4F"/>
<stop offset="0.8993" stop-color="#4C4C4D"/>
<stop offset="1" stop-color="#4C4C4D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,17 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_8968)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8968" x1="51.9669" y1="34.0483" x2="51.9669" y2="5.975" gradientUnits="userSpaceOnUse">
<stop stop-color="#E7E7E8"/>
<stop offset="0.2851" stop-color="#E5E5E6"/>
<stop offset="0.4267" stop-color="#DDDDDE"/>
<stop offset="0.5368" stop-color="#D0D0D1"/>
<stop offset="0.6305" stop-color="#BDBDBE"/>
<stop offset="0.7139" stop-color="#A5A5A6"/>
<stop offset="0.7899" stop-color="#878788"/>
<stop offset="0.8589" stop-color="#646465"/>
<stop offset="0.8993" stop-color="#4C4C4D"/>
<stop offset="1" stop-color="#4C4C4D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,17 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_8930)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8930" x1="51.9669" y1="34.0648" x2="51.9669" y2="5.99157" gradientUnits="userSpaceOnUse">
<stop stop-color="#CBE9DD"/>
<stop offset="0.2841" stop-color="#C9E7DB"/>
<stop offset="0.4252" stop-color="#C3DFD4"/>
<stop offset="0.5349" stop-color="#B8D2C8"/>
<stop offset="0.6283" stop-color="#A9BFB7"/>
<stop offset="0.7114" stop-color="#96A7A0"/>
<stop offset="0.7871" stop-color="#7D8985"/>
<stop offset="0.8559" stop-color="#616665"/>
<stop offset="0.8993" stop-color="#4C4C4D"/>
<stop offset="1" stop-color="#4C4C4D"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_8918)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8918" x1="51.976" y1="34.0522" x2="51.976" y2="5.97896" gradientUnits="userSpaceOnUse">
<stop stop-color="#CADCDD"/>
<stop offset="0.2653" stop-color="#C8DBDB"/>
<stop offset="0.3972" stop-color="#C0D6D5"/>
<stop offset="0.4996" stop-color="#B3CFCB"/>
<stop offset="0.5869" stop-color="#A0C4BD"/>
<stop offset="0.6645" stop-color="#88B6AA"/>
<stop offset="0.7353" stop-color="#6AA593"/>
<stop offset="0.8009" stop-color="#469177"/>
<stop offset="0.8607" stop-color="#1E7A58"/>
<stop offset="0.8993" stop-color="#006941"/>
<stop offset="1" stop-color="#006941"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_8972)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8972" x1="52.0031" y1="34.0483" x2="52.0031" y2="5.975" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0CDC2"/>
<stop offset="0.2632" stop-color="#CECCC1"/>
<stop offset="0.394" stop-color="#C6C8BC"/>
<stop offset="0.4956" stop-color="#B9C2B4"/>
<stop offset="0.5823" stop-color="#A6B9A8"/>
<stop offset="0.6593" stop-color="#8EAD99"/>
<stop offset="0.7295" stop-color="#709F86"/>
<stop offset="0.7947" stop-color="#4C8E70"/>
<stop offset="0.854" stop-color="#247A57"/>
<stop offset="0.8993" stop-color="#006941"/>
<stop offset="1" stop-color="#006941"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,18 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="url(#paint0_linear_841_8894)" stroke="black"/>
<defs>
<linearGradient id="paint0_linear_841_8894" x1="51.976" y1="34.0648" x2="51.976" y2="5.99157" gradientUnits="userSpaceOnUse">
<stop stop-color="#CBE9DD"/>
<stop offset="0.265" stop-color="#C9E8DB"/>
<stop offset="0.3966" stop-color="#C1E3D5"/>
<stop offset="0.4989" stop-color="#B4DACB"/>
<stop offset="0.5861" stop-color="#A1CFBD"/>
<stop offset="0.6636" stop-color="#89BFAA"/>
<stop offset="0.7343" stop-color="#6BAC93"/>
<stop offset="0.7998" stop-color="#479678"/>
<stop offset="0.8595" stop-color="#1F7D59"/>
<stop offset="0.8993" stop-color="#006941"/>
<stop offset="1" stop-color="#006941"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="#CBDDDE" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="#D1CEC3" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="#5D564C" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="#D3D3D3" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="#E9E9EB" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="#CBE9DD" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="white" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

View file

@ -0,0 +1,3 @@
<svg width="104" height="40" viewBox="0 0 104 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.9768 32.4623C54.8365 32.4623 73.392 32.1997 87.6574 34.0091C90.1732 34.3302 91.889 32.1711 91.9744 30.7698C92.5182 22.8924 84.227 12.0979 84.227 12.0979C84.227 12.0979 81.7113 8.18778 75.6493 7.42988C75.6493 7.42988 61.3546 6 52.0061 6C43.2576 6 28.3617 7.42988 28.3617 7.42988C22.3009 8.18778 19.7851 12.0967 19.7851 12.0967C19.7851 12.0967 11.3793 22.9222 12.0366 30.7698C12.1232 31.9371 13.8378 34.3302 16.3536 34.0091C30.5641 32.1997 49.1184 32.4623 51.9768 32.4623Z" fill="#626167" stroke="black"/>
</svg>

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -0,0 +1,36 @@
const analyticsPageEvents = {
ENTRY: "ENTRY",
EVENT: "EVENT",
};
// GA Constants
const GaEvents = {
GENERIC_EVENT: "event",
PAGE_VIEW_EVENT: "logPageview",
};
const GaCategories = {
API_RESPONSE: "Api_Response",
EVOX: "Evox",
};
const GaActions = {
RESULT: "Result",
CLICKED: "Clicked",
VIF: "vif",
SUBMITTED: "Submitted",
};
const GaLabels = {
SUCCESS: "Success",
ERROR: "Error",
LICENSE_PLATE_LOOKUP: "License_Plate_Look_Up",
VIN_LOOKUP: "Vin_Look_Up",
ADDRESS_LOOKUP: "Address_Look_up",
};
const ValueToLogTypes = {
LAST_5: "last_5",
};
export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes };

View file

@ -0,0 +1,17 @@
const applicationConfig = {
CURRENT_ENVIRONMENT: process.env.VUE_APP_CURRENT_ENVIRONMENT, // "Localhost", "Dev", "QA", and "Prod"
CONSUMER_CF_DISTRO: process.env.VUE_APP_CONSUMER_CF_DISTRO,
ANALYTICS_SESSION_TIMEOUT_MINUTES: 30,
SAVED_SESSION_TIMEOUT_DAYS: 45,
COOKIE_PATH: "/",
APPLICATION_NAME: "SelfService",
SITE_ENTRY_TRIGGER_VALUE: "SelfService",
APPLICATION_ABBREVIATION: "iss",
PAGE_QUERYSTRING: 'issPage',
CLIENTTAG_QUERYSTRING: 'CientTag',
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
ISS_DEV_CMS_DOMAIN: "https://digitalisscms.dev.safelite.io",
CASH_PARENT_ACCOUNT_NUMBER: 167132
};
export { applicationConfig };

View file

@ -0,0 +1,12 @@
import { applicationConfig } from "@/constants/application-config.js";
const cookieNames = {
ISS_SESSION_INFO: `ISSSessionInfo-${applicationConfig.CURRENT_ENVIRONMENT}`,
// Existing Safelite.com cookies
DXDEV: "dxdev",
SESSION_ID: "sid",
SESSION_KEY: "skey",
};
export { cookieNames };

View file

@ -0,0 +1,7 @@
const coverageStatuses = {
PENDING: "Pending",
NO_COMP: "No Comp",
VERIFIED: "Verified",
};
export { coverageStatuses };

View file

@ -0,0 +1,8 @@
const damageCustomLabels = Object.freeze({
MATCH: 'match',
REAR_WINDOW: 'rear window',
SIDE_WINDOW: 'side window',
WINDSHIELD: 'windshield'
});
export default damageCustomLabels;

View file

@ -0,0 +1,9 @@
const damageLocationsCms = {
WINDSHIELD: "WINDSHIELD",
SIDEDOOR: "SIDEDOOR",
REARWINDOW: "REARWINDOW",
DRIVERSIDE: "DRIVERSIDE",
PASSENGERSIDE: "PASSENGERSIDE"
};
export { damageLocationsCms };

View file

@ -0,0 +1,21 @@
const damageLocationsSelected = {
WINDSHIELD: "Windshield",
SIDEDOOR: "SideDoor",
REARWINDOW: "RearWindow",
REPAIR: "Repair",
REPLACE: "Replace",
DRIVER: "Driver",
PASSENGER: "Passenger",
FRONT: "Front",
REAR: "Rear",
BACK: "Back",
QUARTER: "Quarter",
VENT: "Vent",
SINGLE: "Single",
DRIVERSIDE: "DriverSide",
PASSENGERSIDE: "PassengerSide",
STATIONARY: "Stationary",
SLIDER: "Slider"
};
export { damageLocationsSelected };

View file

@ -0,0 +1,10 @@
const dynamicStrings = {
GLOBAL_STATE: "globalState",
CUSTOM: "custom",
ROUTER_LINK: "routerLink:",
MODAL_LINK: "modalLink",
TEXT_LINK: 'textLink',
EXTERNAL_LINK: 'externalLink',
};
export { dynamicStrings };

View file

@ -0,0 +1,56 @@
// Used to support custom (non-state) related dynamic text.
// Example: {custom:glassName}, each key in the array is the value after custom:, like 'glassname'
// Please use lowercase only so that we don't have to worry about case sensitivity.
const customMappings = {
formattedglassname: [
{ key: "Windshield Single", transformedValue: "windshield" },
{ key: "Windshield Driver", transformedValue: "driver side split windshield" },
{ key: "Windshield Passenger", transformedValue: "passenger side split windshield" },
{ key: "Rear Stationary", transformedValue: "rear window" },
{ key: "Rear Slider", transformedValue: "rear window" },
{ key: "Driver Front", transformedValue: "driver side front door" },
{ key: "Driver Back", transformedValue: "driver side back door" },
{ key: "Driver Vent", transformedValue: "driver side vent glass" },
{ key: "Driver Quarter", transformedValue: "driver side quarter panel" },
{ key: "Driver SideDoor", transformedValue: "driver side sliding door" },
{ key: "Passenger Front", transformedValue: "passenger side front door" },
{ key: "Passenger Back", transformedValue: "passenger side back door" },
{ key: "Passenger Vent", transformedValue: "passenger side vent glass" },
{ key: "Passenger Quarter", transformedValue: "passenger side quarter panel" },
{ key: "Passenger SlideDoor", transformedValue: "passenger side sliding door" }
]
};
// Gets an instance of a string where the dynamic portion of the text {custom:KeyName}
// is replaced by a value from the above map.
// If the value isn't found, return the original dynamic string without replacement
export function getCustomTransformValue(dynamicString, key) {
// Get array key from the dynamic string
const regexExp = new RegExp("{(.*?):(.*?)}", "g");
const matches = [...dynamicString.matchAll(regexExp)];
if (matches.length === 0) {
return dynamicString;
}
const arrayKey = matches[0][2];
// Get the array of possible values based on the key name.
const transformArray = customMappings[arrayKey.toLowerCase()];
if (transformArray === undefined) {
return dynamicString;
}
// Get the value where the name matches the key name, there should only be one so find() is used.
const mapObject = transformArray.find((map) => map.key.toLowerCase() === key.toLowerCase());
if (mapObject === undefined) {
return dynamicString;
}
const finalString = dynamicString.replace(`{custom:${arrayKey}}`, mapObject.transformedValue);
return finalString;
}

View file

@ -0,0 +1,9 @@
const endorsementOptions = {
EDUCATOR: "Educator",
OEM_APPROVED: "OEM Approved",
FULL_GLASS: "Full Glass Coverage",
PARKING_GUARD: "Parking Guard",
REPAIR_WAIVED: "Repair Waived"
};
export { endorsementOptions };

136
src/constants/endpoints.js Normal file
View file

@ -0,0 +1,136 @@
const endpoints = {
GetRouteInfo: {
url: (applicationAbbreviation) => `/content/api/v1/content/${applicationAbbreviation}/RouteInfo`,
method: 'POST'
},
GetHomepageInfo: {
url: (applicationAbbreviation) => `/content/api/v1/content/${applicationAbbreviation}/HomepageInfo`,
method: 'GET'
},
GetPageData: {
url: (applicationAbbreviation, pageName) => `/content/api/v1/content/${applicationAbbreviation}/${pageName}`,
method: 'GET'
},
GetVehicleYears: {
url: '/vehicle/api/v1/vehicle/years',
method: 'GET'
},
GetVehicleMakes: {
url: '/vehicle/api/v1/vehicle/makes/',
method: 'GET'
},
GetVehicleModels: {
url: '/vehicle/api/v1/vehicle/models',
method: 'GET'
},
GetVehicleStyles: {
url: '/vehicle/api/v1/vehicle/styles',
method: 'GET'
},
GetDamageOptions: {
url: '/parts/api/v1/parts/damage-options',
method: 'GET'
},
GetPartsOrQuestions: {
url: '/parts/api/v1/parts/parts-or-questions',
method: 'POST'
},
GetParts: {
url: '/parts/api/v1/parts/parts',
method: 'POST'
},
GetPriceOrderItems: {
url: '/price/api/v1/price/order-items',
method: 'GET'
},
GetCapabilityQuestions: {
url: '/parts/api/v1/parts/capability-questions',
method: 'GET'
},
GetPartFromCapabilityAnswer: {
url: '/parts/api/v1/parts/part-from-capability-answer',
method: 'POST'
},
GetWipers: {
url: '/parts/api/v1/parts/wipers',
method: 'GET'
},
GetRainDefense: {
url: '/parts/api/v1/parts/rain-defense',
method: 'GET'
},
GetSupportingItems: {
url: '/parts/api/v1/parts/supporting-items',
method: 'POST'
},
GetServiceabilityDetails: {
url: "/location/api/v1/location/serviceability-details",
method: "GET",
},
GetVehicle: {
url: '/vehicle/api/v1/vehicle/lookup',
method: 'GET'
},
LogExperimentExposureIfAssigned: {
url: '/experiments/api/v1/experiments/log-exposure',
method: 'POST'
},
LogPageView: {
url: '/analytics/api/v1/analytics/log-page-view',
method: 'POST'
},
LogCustomEvent: {
url: '/analytics/api/v1/analytics/log-custom-event',
method: 'POST'
},
LookupVehicleByVin: {
url: '/vehicle/api/v1/vehicle/lookup',
method: 'POST'
},
LookupVinByAddress: {
url: '/vehicle/api/v1/vehicle/lookup-vin-by-address',
method: 'POST'
},
LookupVinByPlate: {
url: '/vehicle/api/v1/vehicle/lookup-vin-by-plate',
method: 'POST'
},
InitializeSession: {
url: '/analytics/api/v1/analytics/initialize',
method: 'POST'
},
GetExperimentsByUser: {
url: '/analytics/api/v1/analytics/get-experiments',
method: 'GET'
},
RunExperimentsForTrigger: {
url: '/experiments/api/v1/experiments/run',
method: 'POST'
},
ValidateZip: {
url: '/location/api/v1/location/zip',
method: 'GET'
},
GooglePlaces: {
url: 'https://maps.googleapis.com/maps/api/js?key={apiKey}&libraries=places'
},
ValidateClientTag: {
url: '/clientauth/api/v1/clientauth/validate-client-tag',
method: 'GET'
},
IsVinbyAddressPermissible:{
url:'/vehicle/api/v1/vehicle/is-vin-by-address-permissible',
method:'Get'
},
CoveragePolicyInfo: {
url: '/coverage/api/v1/coverage/get-policy-information',
method: 'POST'
},
RegisterClaim: {
url: '/coverage/api/v1/coverage/register-claim',
method: 'POST'
}
};
export { endpoints };

View file

@ -0,0 +1,49 @@
const errorMessages = {
DAMAGE_LOCATION_REQUIRED: "Please select damage location",
DAMAGE_SIDE_REQUIRED: "Please select vehicle side",
DRIVER_SIDE_OPTIONS_REQUIRED: "Please select window",
PASSENGER_SIDE_OPTIONS_REQUIRED: "Please select window",
WINDSHIELD_DAMAGE_TYPE_REQUIRED: "Please select windshield damage",
WINDSHIELD_CHIP_COUNT_REQUIRED: "Please select chip(s)",
WINSHIELD_REPLACE_OPTIONS_REQUIRED: "Please select windshield part",
REPLACE_OPTIONS_REQUIRED: "Please select rear window type",
STREET_ADDRESS_REQUIRED: "Please enter your street address",
CITY_REQUIRED: "Please enter your city",
STATE_REQUIRED: "Please enter your state",
ZIP_REQUIRED: "Please enter your ZIP",
ZIP_FORMAT: "Please enter a valid ZIP",
LICENSE_PLATE_REQUIRED: "Please enter your license plate number",
REGISTRATION_ZIP_REQUIRED: "Please enter your registration ZIP code",
FIRST_NAME_REQUIRED: "Please enter your first name",
LAST_NAME_REQUIRED: "Please enter your last name",
EMAIL_ADDRESS_REQUIRED: "Please enter your email address",
EMAIL_ADDRESS_FORMAT: "Please enter a valid email address",
SERVICE_ZIP_REQUIRED: "Please enter your service ZIP",
SERVICE_ZIP_FORMAT: "Please enter a valid service ZIP",
VIN_REQUIRED: "Please enter your VIN",
VIN_FORMAT: "Invalid VIN. Please make sure that you entered the correct 17-digit, alpha-numeric number. VINs do not contain the letters I, O, or Q",
OPTION_REQUIRED: "Please select an option",
VEHICLE_REQUIRED: "Please select a vehicle",
POLICY_NUMBER_REQUIRED: "Please enter your policy number",
PHONE_NUMBER_REQUIRED: "Please enter phone number",
PHONE_NUMBER_FORMAT: "Please enter your phone number. The format must be ###-###-####",
POLICY_ZIP_REQUIRED: "Please enter your policy ZIP",
POLICY_ZIP_FORMAT: "Please enter a valid ZIP",
LOSS_CAUSE_REQUIRED: "Please enter loss cause",
LOSS_CITY_REQUIRED: "Please enter a city",
LOSS_STATE_REQUIRED: "Please select an option",
LOSS_DATE_REQUIRED: "Please select a date. Format must be MM/DD/YYYY and the date must be not in the future",
DAMAGE_OPTION_REQUIRED: "Please select an option",
POLICYHOLDER_FIRST_NAME_REQUIRED: "Please enter the policyholder first name",
POLICYHOLDER_LAST_NAME_REQUIRED: "Please enter the policyholder last name",
ACKNOWLEDGEMENT_REQUIRED: "You must agree to the terms to continue",
YEAR_REQUIRED: "Please select your vehicle year",
MAKE_REQUIRED: "Please select your vehicle make",
MODEL_REQUIRED: "Please select your vehicle model",
STYLE_REQUIRED: "Please select your vehicle style"
};
export { errorMessages };

17
src/constants/events.js Normal file
View file

@ -0,0 +1,17 @@
const globalEvents = {
Categories: {
GLOBAL_ALERT: "GLOBAL_ALERT"
},
SubCategories: {
PAGE_NOT_FOUND: "PAGE_NOT_FOUND"
}
};
const globalEventTypes = {
Success: "alert-success",
Warning: "alert-warning",
Info: "alert-info",
Danger: "alert-danger"
};
export { globalEvents, globalEventTypes };

View file

@ -0,0 +1,14 @@
const experimentUniverses = {
ISS_FUNNEL: "ISSFunnel"
};
const experimentSettings = {
GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index"
};
const experimentTriggers = {
SITE_ENTRY: "SiteEntry",
PAGE_ENTRY: "PageEntry"
};
export { experimentUniverses, experimentSettings, experimentTriggers };

View file

@ -0,0 +1,3 @@
export const headerKeys = {
EXPERIMENT: "X-Experiment-Data"
};

View file

@ -0,0 +1,12 @@
//used until real services are available
const endpoints = {
GetRouteInfo: {
url: "https://mockey.qa.sagaws.net/service/ISS/Content/RouteInfo",
method: "GET"
}
};
export { endpoints };

View file

@ -0,0 +1,8 @@
const partTypeStrings = {
FRONT_WIPER: 'FRONT WIPER',
REAR_WIPER: 'REAR WIPER',
RAIN_DEFENSE: 'RAIN DEFENSE',
RECALIBRATION: 'RECALIBRATION'
};
export { partTypeStrings };

View file

@ -0,0 +1,5 @@
const queryStrings = {
ISS_PAGE: 'issPage',
};
export { queryStrings };

53
src/constants/states.js Normal file
View file

@ -0,0 +1,53 @@
export const states = {
AL: "Alabama",
AK: "Alaska",
AZ: "Arizona",
AR: "Arkansas",
CA: "California",
CO: "Colorado",
CT: "Connecticut",
DE: "Delaware",
DC: "District Of Columbia",
FL: "Florida",
GA: "Georgia",
HI: "Hawaii",
ID: "Idaho",
IL: "Illinois",
IN: "Indiana",
IA: "Iowa",
KS: "Kansas",
KY: "Kentucky",
LA: "Louisiana",
ME: "Maine",
MD: "Maryland",
MA: "Massachusetts",
MI: "Michigan",
MN: "Minnesota",
MS: "Mississippi",
MO: "Missouri",
MT: "Montana",
NE: "Nebraska",
NV: "Nevada",
NH: "New Hampshire",
NJ: "New Jersey",
NM: "New Mexico",
NY: "New York",
NC: "North Carolina",
ND: "North Dakota",
OH: "Ohio",
OK: "Oklahoma",
OR: "Oregon",
PA: "Pennsylvania",
RI: "Rhode Island",
SC: "South Carolina",
SD: "South Dakota",
TN: "Tennessee",
TX: "Texas",
UT: "Utah",
VT: "Vermont",
VA: "Virginia",
WA: "Washington",
WV: "West Virginia",
WI: "Wisconsin",
WY: "Wyoming"
};

View file

@ -0,0 +1,94 @@
// TintMap with array keys by location, lowercase to avoid as much string mismatching as possible.
// Src assumes you have a @/assets/img/tints/, making the final value @/assets/img/tints/{Src} in the markup.
// See vehicle-parts for implementation example.
const tintMap = {
other: [
// Blue Shade
{ name: "blue tint, blue shade", src: "Glass-BlueShade-BlueTint.svg" },
{ name: "brown tint, blue shade", src: "Glass-BlueShade-BrownTint.svg" },
{ name: "gray tint, blue shade", src: "Glass-BlueShade-GrayTint.svg" },
{ name: "green tint, blue shade", src: "Glass-BlueShade-GreenTint.svg" },
{ name: "clear, blue shade", src: "Glass-BlueShade-NoTint.svg" },
// Brown Shade
{ name: "brown tint, brown shade", src: "Glass-BrownShade-BrownTint.svg" },
// Gray Shade
{ name: "blue tint, gray shade", src: "Glass-GrayShade-BlueTint.svg" },
{ name: "brown tint, gray shade", src: "Glass-GrayShade-BrownTint.svg" },
{ name: "gray tint, gray shade", src: "Glass-GrayShade-GrayTint.svg" },
{ name: "green tint, gray shade", src: "Glass-GrayShade-GreenTint.svg" },
// Green Shade
{ name: "blue tint, green shade", src: "Glass-GreenShade-BlueTint.svg" },
{ name: "brown tint, green shade", src: "Glass-GreenShade-BrownTint.svg" },
{ name: "green tint, green shade", src: "Glass-GreenShade-GreenTint.svg" },
// Tints Only
{ name: "blue tint privacy", src: "Glass-NoShade-BlueTint.svg" },
{ name: "bronze tint", src: "Glass-NoShade-BrownTint.svg" },
{ name: "dark brown tint", src: "Glass-NoShade-DarkBrownTint.svg" },
{ name: "privacy, black frame", src: "Glass-NoShade-Privacy.svg" },
{ name: "gray tint", src: "Glass-NoShade-GrayTint.svg" },
{ name: "green tint", src: "Glass-NoShade-GreenTint.svg" },
{ name: "gray tint privacy", src: "Glass-NoShade-GrayTint.svg" },
// No shade or tint
{ name: "clear", src: "Glass-NoShade-NoTint.svg" }
],
windshield: [
// Blue Shade
{ name: "blue tint, blue shade", src: "Windshield-BlueShade-BlueTint.svg" },
{ name: "bronze tint, blue shade", src: "Windshield-BlueShade-BrownTint.svg" },
{ name: "gray tint, blue shade", src: "Windshield-BlueShade-GrayTint.svg" },
{ name: "green tint, blue shade", src: "Windshield-BlueShade-GreenTint.svg" },
{ name: "clear, blue shade", src: "Windshield-BlueShade-NoTint.svg" },
// Brown Shade
{ name: "bronze tint, bronze shade", src: "Windshield-BrownShade-BrownTint.svg" },
// Gray Shade
{ name: "blue tint, gray shade", src: "Windshield-GrayShade-BlueTint.svg" },
{ name: "brown tint, gray shade", src: "Windshield-GrayShade-BrownTint.svg" },
{ name: "gray tint, gray shade", src: "Windshield-GrayShade-GrayTint.svg" },
{ name: "green tint, gray shade", src: "Windshield-GrayShade-GreenTint.svg" },
// Green Shade
{ name: "blue tint, green shade", src: "Windshield-GreenShade-BlueTint.svg" },
{ name: "brown tint, green shade", src: "Windshield-GreenShade-BrownTint.svg" },
{ name: "green tint, green shade", src: "Windshield-GreenShade-GreenTint.svg" },
// Tints Only
{ name: "blue tint", src: "Windshield-NoShade-BlueTint.svg" },
{ name: "bronze tint", src: "Windshield-NoShade-BrownTint.svg" },
{ name: "dark brown tint", src: "Windshield-NoShade-DarkBrownTint.svg" },
{ name: "privacy, black frame", src: "Windshield-NoShade-Privacy.svg" },
{ name: "gray tint", src: "Windshield-NoShade-GrayTint.svg" },
{ name: "green tint", src: "Windshield-NoShade-GreenTint.svg" },
{ name: "gray tint privacy", src: "Windshield-NoShade-GrayTint.svg" },
// No shade or tint
{ name: "clear", src: "Windshield-NoShade-NoTint.svg" }
]
};
// Gets the tint image source string given the glass location, and the tint description (like 'Green Tint')
// Use lowered strings here to try to avoid mismatch. Returns an empty string if array key doesn't exist.
// Returns undefined if no items are found.
export function getTintImage(glassLocation, colorString) {
// Windshield glass has special images, all other glass uses the same though.
if (glassLocation.toLowerCase() !== "windshield") {
glassLocation = "other";
}
if (tintMap[glassLocation.toLowerCase()] === undefined) {
return "";
}
const tintImageSource = tintMap[glassLocation.toLowerCase()].find(
(item) => item.name.toLowerCase() == colorString.toLowerCase()
);
return tintImageSource;
}

View file

@ -0,0 +1,11 @@
const vehicleCategories = {
CAR: "CAR",
TRUCK: "TRUCK",
VAN: "VAN",
COMMERCIALVAN: "COMMERCIAL VAN",
SUV: "SUV",
MOTORHOME: "MOTOR HOME",
SEMI: "SEMI"
};
export { vehicleCategories };

View file

@ -0,0 +1,8 @@
const vehicleLookupAlertTypes = Object.freeze({
NOT_FOUND: 'notFound',
NOT_MATCHED: 'notMatched',
TWO_IDENTICAL_YMM_MATCHED:'twoIdenticalYMMMatched',
PERFECT_MATCH: 'perfectMatch'
});
export default vehicleLookupAlertTypes;

View file

@ -0,0 +1,6 @@
const vehicleSelectionOptions = Object.freeze({
VEHICLE_NOT_LISTED: 'Vehicle not listed',
});
export {vehicleSelectionOptions};

View file

@ -0,0 +1,7 @@
const vinLookupMethodSelections = Object.freeze({
MANUALVIN: 'ManualVin',
LICENSEPLATE: 'LicensePlate',
HOMEADDRESS: 'HomeAddress'
});
export {vinLookupMethodSelections};

View file

@ -0,0 +1,170 @@
<template>
<label
:class="[buttonWrapperClasses, { 'has-error': errors.length > 0 &&!suppressError,
selected: isChecked }]"
:for="buttonId"
@focusin="handleFocus"
@focusout="handleBlur"
@mousedown.left="handleEventAction(eventTypes.CLICK, $event)">
<input
:type="inputType"
:id="buttonId"
:key="buttonId"
:name="groupName"
:class="inputClasses"
:aria-required="isRequired"
:value="value"
:checked="isChecked"
@keypress.space="handleEventAction(eventTypes.SPACE, $event)"
@keypress.enter="handleEventAction(eventTypes.ENTER, $event)"
@change="handleEventAction(eventTypes.CHANGE, $event)" />
<slot></slot>
</label>
</template>
<script>
import { useField } from "vee-validate";
import { toRef } from "vue";
import {
handleButtonComponentFocus,
handleInputComponentBlur,
} from "@/helpers/button-question-focus-helper";
import { inputButtonProps } from "@/digital-components/base-input-button/button-functionality-props";
export default {
name: "base-input-button",
props: {
...inputButtonProps,
buttonWrapperClasses: [String, Array, Object],
inputClasses: [String, Array, Object],
},
data() {
return {
valueToEmit: null,
};
},
mounted() {
if (this.isChecked) {
this.handleChange(this.modelValue);
}
},
methods: {
handleEventAction(eventType, e) {
if (this.isMultiSelect) {
switch (eventType) {
case this.eventTypes.CHANGE:
this.handleClick(e);
break;
}
} else {
switch (eventType) {
case this.eventTypes.CLICK:
case this.eventTypes.ENTER:
case this.eventTypes.SPACE:
this.handleClick(e);
break;
case this.eventTypes.CHANGE:
this.selectingInitiatesLoad
? this.handleSelectionChange(e)
: this.handleClick(e);
break;
}
}
},
handleSelectionChange(e) {
if (
this.isMultiSelect &&
(this.modelValue instanceof Array || this.modelValue == null)
) {
let newValue = this.modelValue ? [...this.modelValue] : [];
if (!newValue.includes(this.value)) {
newValue.push(this.value);
} else {
newValue.splice(newValue.indexOf(this.value), 1);
}
this.valueToEmit = newValue;
} else if (!this.isMultiSelect) {
this.valueToEmit = this.value;
}
this.handleChange(this.valueToEmit);
},
handleClick(e) {
this.handleSelectionChange(e);
this.$emit("update:modelValue", this.valueToEmit);
},
handleFocus() {
handleButtonComponentFocus({
groupName: this.groupName,
});
},
handleBlur() {
handleInputComponentBlur({
groupName: this.groupName,
onButtonQuestionLostFocusCallback: this.handlePushClickEventToGACheck,
});
},
},
computed: {
isChecked() {
if (this.isMultiSelect && this.modelValue instanceof Array) {
return this.modelValue.includes(this.value);
} else if (!this.isMultiSelect) {
return this.modelValue == this.value;
} else {
return false;
}
},
inputType() {
return this.isMultiSelect ? "checkbox" : "radio";
},
buttonId() {
return `${this.groupName}-${this.value}`.replaceAll(" ", "-");
},
isValueSelectedOnClick() {
return this.isMultiSelect || this.selectingInitiatesLoad;
},
eventTypes() {
return {
CHANGE: "change",
ENTER: "enter",
SPACE: "space",
CLICK: "click",
};
},
},
setup(props) {
const inputType = props.isMultiSelect ? "checkbox" : "radio";
const fieldOptions = {
type: inputType,
validateOnValueUpdate: false,
validateOnMount: false,
};
const { handleChange, meta, errors } = useField(
toRef(props, "groupName"),
toRef(props, "validationRules"),
fieldOptions
);
return {
handleChange,
errors,
meta,
fieldOptions, // only need to expose this for unit test purposes
};
},
};
</script>
<style lang="scss" scoped>
input {
opacity: 0;
height: 0.1px; // NOTE: cannot be zero or Safari can't put focus on it
width: 0;
position: absolute;
}
</style>

View file

@ -0,0 +1,38 @@
export const inputButtonProps = {
value: {
type: [String, Number],
required: true,
},
modelValue: {
required: true,
validator: function(prop) {
return (
prop === null
|| Array.isArray(prop)
|| typeof prop === 'string'
|| (typeof prop === 'number' && !Number.isNaN(prop))
)
}
},
isMultiSelect: Boolean,
groupName: {
type: String,
required: true,
},
validationRules: {
type: String,
default: "",
},
valueToLogType: String,
isRequired: {
type: Boolean,
default: true,
},
lastValuePushedToGa: [String, Number],
setLastValuePushedToGa: Function,
selectingInitiatesLoad: {
type: Boolean,
default: false,
},
suppressError: Boolean,
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,302 @@
<!-- Documented in confluence https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Component -->
<template>
<div
:class="
isOverflowScrollable ? 'button-question button-question-overflow' : 'button-question'
">
<div v-if="questionText && answers && answers.length > 0" class="question-text d-flex"
:class="{'small-question-text': isSmallQuestionText,'small-question-label-text':isSmallQuestionLabelText}">
<span class="fw-bold w-100">{{ questionText }}</span>
</div>
<div class="w-100 d-flex justify-content-center">
<fieldset
class="w-100"
:aria-required="isRequired"
:role="isMultiSelect ? 'group' : 'radiogroup'"
:aria-labelledby="formatString(groupName)">
<legend
class="sr-only"
:data-focus-target="formatString(groupName)"
tabindex="-1"
:id="formatString(groupName)">
{{ questionText }}
{{
isMultiSelect && answers && answers.length > 1
? "Select one or more options below."
: "Select an option below."
}}
</legend>
<div :class="getComponentLoopWrapperClasses">
<div
:class="getComponentWrapperClasses"
v-for="answer in buttonsInfo"
:key="answer.value ?? answer">
<component
:is="buttonTypeString"
:buttonLabel="answer.buttonLabel"
:buttonLabelSubCopy="answer.buttonLabelSubCopy"
:buttonBodyCopy="answer.buttonBodyCopy"
:buttonAuxiliaryCopy="answer.buttonAuxiliaryCopy"
:buttonFooterCopy="answer.buttonFooterCopy"
:buttonImage="answer.buttonImage"
:buttonImageId="answer.buttonImageId"
:groupName="answer.groupName"
:isMultiSelect="isMultiSelect"
:value="answer.value"
:selectingInitiatesLoad="selectingInitiatesLoad"
:isWide="isWide"
:validationRules="validationRules"
:textPosition="textPosition"
:additionalButtonStyling="additionalButtonStyling"
:lastValuePushedToGa="lastValuePushedToGa"
:setLastValuePushedToGa="setLastValuePushedToGa"
:suppressError="suppressError"
v-model="selectedValues" />
<!-- For nested questions -->
<transition name="fade" mode="out-in">
<div
v-if="
typeof selectedValues == 'string' &&
selectedValues == answer.value
">
<slot></slot>
</div>
</transition>
</div>
</div>
</fieldset>
</div>
<div v-if="!suppressError" class="row form-test-error mt-1">
<error-message
:name="formatString(groupName)"
></error-message>
</div>
</div>
</template>
<script>
import listButton from "@/ux-components/list-button/list-button";
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
import listCard from "@/ux-components/list-card/list-card";
import radio from "@/ux-components/radio/radio";
import { ErrorMessage } from "vee-validate";
import providerPrefRadio from "@/layouts/provider-preference/provider-pref-radio/provider-pref-radio.vue"
export default {
name: "buttonQuestion",
props: {
buttonTypeString: {
type: String,
default: "listButton",
},
buttonTypeObject: {
type: Object,
default: null,
},
isMultiSelect: Boolean,
groupName: String,
questionText: String,
answers: Array,
textPosition: {
type: String,
default: "text-center",
},
selectingInitiatesLoad: Boolean,
loaderColor: {
type: String,
default: "blue",
},
loaderPosition: {
type: String,
default: "right",
},
isRequired: Boolean,
isOverflowScrollable: Boolean,
isWide: Boolean,
isCashOrInsurance: Boolean,
modelValue: [Array, Number, String],
value: [Number, String],
validationRules: String,
suppressError: Boolean,
useTextForValue: Boolean,
valueToLogType: String,
additionalButtonStyling: String,
isSmallQuestionText: Boolean,
isSmallQuestionLabelText: Boolean,
},
beforeMount() {
if (this.buttonTypeObject) {
this.$options.components[this.buttonTypeString] = this.buttonTypeObject;
}
},
data() {
return {
lastValuePushedToGa: null,
};
},
computed: {
getFieldSetClasses() {
const baseClasses = this.isOverflowScrollable
? "container-fluid overflow-scroll position-absolute px-5 pt-1 py-0"
: this.buttonTypeString == "listCard"
? "w-100"
: "";
const SmallQuestionTextClass = this.isSmallQuestionText
? baseClasses + "small-question-text"
: baseClasses;
const SmallQuestionLabelText = this.isSmallQuestionLabelText
? SmallQuestionTextClass + "small-question-label-text"
: SmallQuestionTextClass;
return SmallQuestionLabelText;
},
getComponentLoopWrapperClasses() {
let classes;
switch (this.buttonTypeString) {
case "listButton":
classes = "w-100";
break;
case "listButtonHorizontal":
classes = "d-flex flex-row p-0";
break;
case "listCard":
classes = "row g-2 justify-content-center";
if (this.isWide) {
classes += " flex-column";
}
break;
case "radio":
classes = "ui-radio d-flex";
break;
case "servicePackageRadio":
classes = "package-main";
break;
case "providerPrefRadio":
classes = "option-main";
break;
}
return classes;
},
getComponentWrapperClasses() {
let classes = "";
classes += this.isWide ? "col-12" : "col";
switch(this.buttonTypeString) {
case "radio":
classes += " radio-button-container";
break;
case "servicePackageRadio":
classes = "package-wrapper";
break;
case "providerPrefRadio":
classes = "option-wrapper";
break;
default:
}
return classes;
},
buttonsInfo() {
return (Array.isArray(this.answers) ? this.answers : [])?.map((answer) => ({
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
altText: answer.altText ?? (answer.Name ? answer.Name : answer),
buttonLabelSubCopy: answer.buttonLabelSubCopy ?? answer.SubText,
buttonBodyCopy: answer.buttonBodyCopy ?? answer.buttonBodyCopy,
buttonAuxiliaryCopy: answer.buttonAuxiliaryCopy ?? answer.buttonAuxiliaryCopy,
buttonFooterCopy: answer.buttonFooterCopy ?? answer.buttonFooterCopy,
buttonImage: answer.buttonImage ?? answer.AnswerImageUrl,
buttonImageId: answer.buttonImageId ?? answer.ImageId,
groupName: this.formatString(this.groupName),
value:
answer.value ??
(this.useTextForValue && answer.Text ? answer.Text : answer.Name) ??
(typeof answer !== "object" ? answer : null),
}));
},
selectedValues: {
get() {
return this.modelValue;
},
set(selectedAnswers) {
this.$emit("update:modelValue", selectedAnswers);
},
},
},
methods: {
formatString(str) {
return String(str).replaceAll(" ", "-");
},
setLastValuePushedToGa(lastValuePushedToGa) {
this.lastValuePushedToGa = lastValuePushedToGa;
},
},
components: {
listButton,
listButtonHorizontal,
listCard,
ErrorMessage,
radio,
providerPrefRadio
},
};
</script>
<style lang="scss">
.button-question {
color: $black;
.radio-button-container {
&:not(:last-child) {
padding-bottom: map-get($spacers, 2);
}
}
}
.question-text {
margin-top: 1.5rem;
margin-bottom: 1rem;
font-size: 1rem;
line-height: 1.625rem;
& > span {
text-align: center;
}
}
.small-question-text {
&.question-text {
span {
font-size: 0.875rem;
text-align: left;
margin: 0 0 0.5rem 0;
}
}
&.question-text {
margin: 0;
}
&fieldset {
.ui-radio {
margin: 0;
}
}
}
.small-question-label-text {
&.question-text {
span {
text-align: left;
margin: 0 0 0.25rem 0;
}
}
&.question-text {
margin: 0;
}
&fieldset {
.ui-radio {
margin: 0;
}
}
}
</style>

View file

@ -0,0 +1,170 @@
import { shallowMount } from "@vue/test-utils";
import dropdownQuestion from "./dropdown-question";
// Mock CMS content
const questionText = "Question Text";
const mockMixin = {
methods: {
getCmsContent: jest.fn().mockImplementation(()=> {
return questionText;
})
}
}
// TODO: Remove the following from dropdown-question.vue -> :class="(errors && errors.length) || hasError ? 'has-error' : ''"
// It is not being used.
describe("dropdownQuestion.vue", () => {
it("Should render a select input", async () => {
// Arrange
const wrapper = shallowMount(dropdownQuestion, {
propsData: {
options: {},
},
mixins: [mockMixin]
});
wrapper.getCmsContent = jest.fn();
// Act
const select = wrapper.find("select");
// Assert
expect(select.exists()).toBe(true);
});
it("Should render the 'questionText' data value as the label text.", async () => {
// Arrange
const wrapper = shallowMount(dropdownQuestion, {
propsData: {
options: {},
},
mixins: [mockMixin]
});
// Act
const label = wrapper.find("label");
// Assert
expect(label.text()).toContain(questionText);
});
it("Should render the 'questionText' data value with '&NoBreak;' after the first character of each word in the label text when disableAutoFill is true.", async () => {
// Arrange
const wrapper = shallowMount(dropdownQuestion, {
propsData: {
options: {},
disableAutoFill: true,
},
mixins: [mockMixin]
});
// Mock CMS content ...
// Trust me, the below instance of the string "Question Text" actually has the &NoBreak; in it. You just can't see it
// Don't believe me? Copy and paste it into Google. Then inspect the search field element in Dev Tools,
// you will see "Q&NoBreak;uestion T&NoBreak;ext"
const expectedQuestionText = "Question Text";
// Act
const label = wrapper.find("label");
// Assert
expect(label.text()).toContain(expectedQuestionText);
});
it("Should return input id as the id of the select field", async () => {
// Arrange
const wrapper = shallowMount(dropdownQuestion, {
propsData: {
inputId: "input ID",
options: {},
},
mixins: [mockMixin]
});
// Act
const select = wrapper.find("select");
// Assert
expect(select.attributes().id).toEqual("input ID");
});
it("Should render the 'questionText' data value as the aria-label attribute.", async () => {
// Arrange
const wrapper = shallowMount(dropdownQuestion, {
propsData: {
options: {},
},
mixins: [mockMixin]
});
// Act
const label = wrapper.find("label");
// Assert
expect(label.attributes("aria-label")).toContain(questionText);
});
it("Should return aria-disabled state as disabled", async () => {
// Arrange
const wrapper = shallowMount(dropdownQuestion, {
propsData: {
options: {},
isDisabled: true,
},
mixins: [mockMixin]
});
// Act
const select = wrapper.find("select");
// Assert
expect(select.attributes("aria-disabled")).toEqual("true");
});
it("Should emit new value when modelValue is changed", async () => {
// Arrange
const wrapper = shallowMount(dropdownQuestion, {
propsData: {
options: {},
modelValue: "val",
},
mixins: [mockMixin]
});
// Act
await wrapper.find("select").setValue("val2");
// Assert
expect(wrapper.emitted()).toHaveProperty('change')
});
it("Should call this.handleChange with new value when selectedOption is changed", async () => {
// Arrange
const wrapper = shallowMount(dropdownQuestion, {
propsData: {
options: {},
modelValue: "0",
},
mixins: [mockMixin]
});
wrapper.vm.handleChange = jest.fn().mockImplementation(() => {});
// Act
wrapper.vm.$options.watch.selectedOption.call(wrapper.vm, 1);
// Assert
expect(wrapper.vm.handleChange).toHaveBeenCalled;
});
});

View file

@ -0,0 +1,164 @@
<template>
<div class="dropdown-question" :class="(errors && errors.length) || hasError ? 'has-error' : ''">
<label
:for="inputId"
:aria-label="questionText"
class="form-label"
v-html="labelText"></label>
<select
v-model="selectedOption"
class="form-select"
:id="inputId"
:name="inputId"
:aria-disabled="isDisabled"
:disabled="isDisabled"
:aria-required="isRequired"
:validationRules="validationRules"
:placeHolderText="placeHolderText">
<option v-if="placeHolderText" value="" selected>{{ placeHolderText }}</option>
<option v-for="(value, name, index) in options" :value="name" :key="index">
{{ value }}
</option>
</select>
<div v-show="errorMessage" class="row mt-1 form-test-error">
<span role="alert">{{ errorMessage }}</span>
</div>
</div>
</template>
<script>
import { useField } from "vee-validate";
export default {
name: "dropdown-question",
props: {
modelValue: String,
inputId: String,
options: {
type: Object,
required: true,
},
isDisabled: Boolean,
isRequired: Boolean,
disableAutoFill: Boolean,
validationRules: String,
cmsWidgetName: String,
hasError: Boolean,
placeHolderText: String
},
setup(props) {
const propsClone = Object.assign({}, props);
const modelValue = propsClone.modelValue;
let initialValue;
switch (typeof modelValue) {
case "number":
initialValue = modelValue;
break;
default:
initialValue = (modelValue && modelValue.length > 0) ? modelValue : "";
break;
}
const fieldOptions = {
type: "select",
value: props.modelValue,
initialValue: initialValue,
};
const {errorMessage, handleBlur, handleChange, meta, errors } = useField(
props.inputId,
props.validationRules,
fieldOptions
);
return {
errorMessage,
handleBlur,
handleChange,
meta,
errors
};
},
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
},
selectedOption: {
get: function () {
return !this.modelValue ? "" : this.modelValue;
},
set: function (newValue) {
this.$emit("update:modelValue", newValue);
},
},
labelText: {
get: function () {
const noBreakChar = "&NoBreak;";
var questionText = "";
if (this.disableAutoFill) {
var words = this.questionText.toString().split(/[ ]+/);
words.forEach(function (word) {
const position = 1;
word = [
word.toString().slice(0, position),
noBreakChar,
word.toString().slice(position),
].join("");
questionText += `${word} `;
});
questionText = questionText.trimEnd();
} else {
questionText = this.questionText.toString();
}
return questionText;
},
},
},
watch: {
selectedOption(newValue) {
this.handleChange(newValue);
},
},
};
</script>
<style lang="scss">
.dropdown-question {
label {
color: $black;
font-weight: 500;
}
.form-label {
margin-bottom: 0.25rem;
}
.form-select {
color: $gray-600;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
border: 1px solid $gray-500;
border-radius: 0.5rem;
min-height: 3rem;
&:focus,
&:focus-visible {
box-shadow: 0 0 0 2.5px $blue;
}
&:disabled,
&.disabled {
background-color: $gray-100;
color: $gray-500;
filter: grayscale(100%);
&:hover {
box-shadow: 0 0 0 4px transparent;
border: 1px solid $gray-500;
}
}
&:hover {
border: 1px solid $gray-500;
box-shadow: 0 0 0 4px $blue-300;
}
}
}
</style>

View file

@ -0,0 +1,255 @@
jest.mock("vee-validate", () => ({
useForm: jest.fn(),
}));
const mockValidate = (returnValue) => jest.fn(async () => Promise.resolve({ valid: returnValue }));
const mockMeta = (returnValue) => jest.fn(async () => Promise.resolve(returnValue));
import { shallowMount } from "@vue/test-utils";
import modal from "./modal";
import crypto from "crypto";
import { useForm } from "vee-validate";
import { Modal } from "bootstrap";
const footerButtonText = "Sample footer text here.";
const headerText = "Sample header text here.";
global.crypto = crypto;
describe("modal.vue", () => {
it("Should display modal header text when headerText is defined", async () => {
// Arrange / Act
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const wrapper = shallowMount(modal, {
props: {
headerText: headerText,
footerButtonText: footerButtonText,
},
attachTo: document.body,
});
// Assert
expect(wrapper.html()).toEqual(expect.stringContaining(headerText));
});
it("Should display footer button text when footerButtonText is defined", async () => {
// Arrange / Act
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
},
attachTo: document.body,
});
// Assert
expect(wrapper.html()).toEqual(expect.stringContaining(footerButtonText));
});
it("Should emit 'footer-button-event' if the form is valid", async () => {
// Arrange
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
const buttonMain = wrapper.findComponent({ ref: "modalButtonMain" });
await buttonMain.trigger("click-event");
// Assert
expect(wrapper.emitted("footer-button-event")).toBeTruthy();
});
it("Should not emit 'footer-button-event' if the form is invalid", async () => {
// Arrange
const fakeMeta = {
touched: true,
dirty: true,
valid: false,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(false),
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
const buttonMain = wrapper.findComponent({ ref: "modalButtonMain" });
await buttonMain.trigger("click-event");
// Assert
expect(wrapper.emitted("footer-button-event")).toBeFalsy();
});
it("Should have a disabled footer button when the form has not been touched", async () => {
// Arrange / Act
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Assert
expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
});
it("Should have a disabled footer button when the form is invalid", async () => {
// Arrange / Act
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Assert
expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
});
it("Should call bootstrap Modal method 'show' when calling 'openModal'", async () => {
// Arrange
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const showMock = jest.spyOn(Modal.prototype, "show");
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
wrapper.vm.openModal();
// Assert
expect(showMock).toHaveBeenCalled();
});
it("Should call bootstrap Modal method 'hide' when calling 'closeModal'", async () => {
// Arrange
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const hideMock = jest.spyOn(Modal.prototype, "hide");
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
wrapper.vm.openModal();
wrapper.vm.closeModal();
// Assert
expect(hideMock).toHaveBeenCalled();
});
});

View file

@ -0,0 +1,187 @@
<template>
<!-- Modal -->
<div
class="modal fade modal-component"
v-on="{ 'hidden.bs.modal': onModalClosed, 'shown.bs.modal': onModalOpened }"
:id="modalId"
tabindex="-1"
aria-labelledby="ModalComponentLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header mb-2 mt-2">
<label
v-if="headerText"
class="modal-title d-flex justify-content-center pb-0 w-100">
{{ headerText }}
</label>
<button
ref="closeButton"
type="button"
class="btn-close"
@mousedown="closeModal"
@keypress.space="closeModal"
aria-label="Close"></button>
</div>
<div class="modal-body ps-5 pe-5 pb-5 pt-0">
<slot></slot>
</div>
<div class="modal-footer px-5 py-4">
<modalButtonMain
isPrimary
class="w-100"
ref="modalButtonMain"
loaderColor="white"
:buttonText="footerButtonText"
@click-event="validateAndEmit"
:class="(isButtonDisabled || isFooterButtonDisabled) && 'form-test-invalid'" />
</div>
</div>
</div>
</div>
</template>
<script>
import modalButtonMain from "@/ux-components/modal-button-main/modal-button-main";
import { Modal } from "bootstrap";
import { useForm } from "vee-validate";
export default {
name: "modal",
props: {
modalId: String,
headerText: String,
footerButtonText: String,
onModalOpenedCallback: {
type: Function,
},
onModalClosedCallback: {
type: Function,
},
isButtonDisabled: Boolean
},
setup(props) {
const modalId = props.modalId ? props.modalId : `modal-${crypto.randomUUID()}`;
const { meta, validate, resetForm } = useForm();
return {
modalId,
meta,
validate,
resetForm,
};
},
methods: {
async validateAndEmit() {
const validationResult = await this.validate();
if (validationResult.valid) {
this.$emit("footer-button-event");
} else {
this.resetButtonStyle();
}
},
resetButtonStyle() {
this.$refs.modalButtonMain?.resetButtonStyle();
},
onModalOpened() {
this.onModalOpenedCallback?.();
},
onModalClosed() {
this.resetButtonStyle();
this.onModalClosedCallback?.();
},
openModal() {
const modal = Modal.getOrCreateInstance(document.getElementById(this.modalId));
modal?.show();
},
closeModal() {
const modal = Modal.getInstance(document.getElementById(this.modalId));
modal?.hide();
},
},
computed: {
isFooterButtonDisabled() {
if (!this.meta.touched) {
return !this.meta.valid;
}
return !this.meta.dirty || !this.meta.valid;
},
},
components: {
modalButtonMain,
},
};
</script>
<style lang="scss">
.modal {
overflow: hidden;
top: auto;
bottom: 0;
h5,
strong,
.subheader-text {
color: $black;
}
.modal-header {
border-bottom: none;
.btn-close {
position: absolute;
right: 1rem;
top: 1rem;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.6874 1.82179L9.50889 8L15.6874 14.1782C16.1042 14.595 16.1042 15.2707 15.6874 15.6875C15.4843 15.8907 15.2146 16 14.9328 16C14.6514 16 14.3818 15.8906 14.1787 15.6875L8.00017 9.50934L1.82171 15.6875C1.6182 15.8907 1.34876 16 1.06708 16C0.785733 16 0.516056 15.8906 0.312965 15.6875C-0.10429 15.2706 -0.10429 14.5952 0.312797 14.1784L6.03276 8.45867L6.49146 8L0.312945 1.82177C-0.10429 1.40483 -0.10429 0.729418 0.312797 0.31262C0.728936 -0.104145 1.40489 -0.104051 1.82185 0.31262L7.54152 6.03202L8.00017 6.49065L14.1786 0.312472C14.5955 -0.104107 15.2707 -0.104201 15.6874 0.312452C16.1042 0.729289 16.1042 1.40496 15.6874 1.82179Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
opacity: 1;
}
.modal-title {
font-weight: 500;
color: $black;
}
}
&.modal-component {
.modal-dialog {
max-width: 576px;
margin: 0 auto;
.modal-content {
margin: 0 auto;
box-shadow: 0px 16px 48px -16px rgba(0, 0, 0, 0.25);
border-radius: 1.5rem 1.5rem 0 0;
.modal-body {
.modal-sub-body {
color: $gray-600;
}
ul {
margin-bottom: 0;
}
p {
&:last-child {
margin-bottom: 0;
}
}
}
}
&.modal-dialog-centered {
align-items: flex-end;
min-height: 100%;
}
}
.modal-footer {
border-top: none;
background-color: $gray-100;
box-shadow: 0px -1px 0px rgba(179, 180, 181, 0.3);
button {
margin: 0;
}
}
}
}
body {
.modal-backdrop {
height: 100%;
&.show {
opacity: 0.4;
}
}
}
</style>

View file

@ -0,0 +1,314 @@
import { shallowMount } from "@vue/test-utils";
import questionChain from "@/digital-components/question-chain/question-chain.vue";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
describe("Question Chain component", () => {
describe("on create...", () => {
test("Should populate questions data array", async () => {
//Arrange
const { wrapper } = setupMocks({});
const expectedFirstQuestionAnswer = {
answerResult: "DB09410",
buttonLabel: "Yes",
nextQuestionSequence: null,
questionSequence: 1,
questionType: "answer",
value: "1|answer|DB09410|Yes",
};
//Act
await nextTick();
//Assert
expect(wrapper.vm.questions[0].answers[0]).toMatchObject(expectedFirstQuestionAnswer);
});
test("Should not include suppressed questions on questions data array", async () => {
//Arrange
const { wrapper } = setupMocks({});
await wrapper.setData({
questionData: [
{
questionSequence: 1,
questionText: "Question here?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
suppressThisQuestion: true,
},
],
});
//Act
await nextTick();
//Assert
expect(wrapper.vm.questions[0]).toBeFalsy();
});
});
describe("method handleAnswer...", () => {
test("should run getQuestionChainAnswerIfComplete", () => {
//Arrange
const { wrapper } = setupMocks({});
const testQuestion = {};
const testReturnedAnswer = "aReturnedAnswer";
wrapper.vm.getQuestionChainAnswerIfComplete = jest.fn();
//Act
wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
//Assert
expect(wrapper.vm.getQuestionChainAnswerIfComplete).toBeCalled();
});
test("should emit update:modelValue if returnedAnswer contains 'answer'", () => {
//Arrange
const { wrapper } = setupMocks({});
const testQuestion = {};
const testReturnedAnswer = "1|answer|DB10840|No";
//Act
wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
//Assert
expect(wrapper.emitted()).toHaveProperty("update:modelValue");
expect(wrapper.emitted()["update:modelValue"][0][0]).toMatchObject({
answerResult: "DB10840",
});
});
test("should NOT emit update:modelValue if no returnedAnswer", () => {
//Arrange
const { wrapper } = setupMocks({});
const testQuestion = {};
//Act
wrapper.vm.handleAnswer(testQuestion);
//Assert
expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
});
test("should NOT emit update:modelValue if returnedAnswer contains 'nextQuestion'", () => {
//Arrange
const { wrapper } = setupMocks({});
const testQuestion = {};
const testReturnedAnswer = "1|nextQuestion|DB10840|No";
//Act
wrapper.vm.handleAnswer(testQuestion, testReturnedAnswer);
//Assert
expect(wrapper.emitted()).not.toHaveProperty("update:modelValue");
});
});
describe("method getQuestionChainAnswerIfComplete...", () => {
test("should return false if no returned answer", () => {
//Arrange
const { wrapper } = setupMocks({});
//Act
const result = wrapper.vm.getQuestionChainAnswerIfComplete();
//Assert
expect(result).toBeFalsy();
});
test("should set the answerSelected to match the answered one", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|answer|DB10840|No";
await wrapper.setData({
questionData: [
{
questionSequence: 1,
questionText: "Question here?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
},
],
});
//Act
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
//Assert
expect(wrapper.vm.questions[0].answerSelected).toBe("1|answer|DB10840|No");
});
test("should remove answers of questions after the answered one", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|answer|DB10840|No";
await wrapper.setData({
questionData: [
{
questionSequence: 1,
questionText: "Question here?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
},
{
questionSequence: 2,
questionText: "Question 2",
answers: [
{
answerResult: "2-yes",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "2-no",
answerText: "No",
nextQuestionSequence: null,
},
],
answerSelected: "previously selected answer",
},
],
});
//Act
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
//Assert
expect(wrapper.vm.questions[1].answerSelected).toBeUndefined;
});
test("should return false if returnedAnswer is a nextQuestion (not a final answer)", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|nextQuestion|DB10840|No";
//Act
const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
//Assert
expect(result).toBeFalsy();
});
test("should trigger scroll to .current-question if returnedAnswer is a nextQuestion (not a final answer)", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|nextQuestion|DB10840|No";
//Act
wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
await nextTick();
//Assert
expect(Element.prototype.scrollIntoView).toHaveBeenCalled();
});
test("should return answer object if returnedAnswer is a final matching answer", async () => {
//Arrange
const { wrapper } = setupMocks({});
const testReturnedAnswer = "1|answer|DB10840|No";
await wrapper.setData({
questions: [
{
questionSequence: 1,
questionText: "Question here?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
},
],
index: 0,
});
//Act
const result = wrapper.vm.getQuestionChainAnswerIfComplete(testReturnedAnswer);
//Assert
expect(result).toMatchObject({
answerResult: "DB10840",
answeredQuestions: [
{ questionNum: 1, questionText: "Question here?", selectedAnswerText: "No" },
{
questionNum: 1,
questionText: "Does only your center sliding piece need to be replaced?",
selectedAnswerText: "No",
},
],
index: 0,
});
});
});
});
function setupMocks({
questionDataProp = [
{
questionSequence: 1,
questionText: "Does only your center sliding piece need to be replaced?",
answers: [
{
answerResult: "DB09410",
answerText: "Yes",
nextQuestionSequence: null,
},
{
answerResult: "DB10840",
answerText: "No",
nextQuestionSequence: null,
},
],
},
],
}) {
Element.prototype.scrollIntoView = jest.fn();
const mountOptions = getMountOptions({});
mountOptions.propsData = {
questionData: questionDataProp,
};
mountOptions["attachTo"] = document.body;
const wrapper = shallowMount(questionChain, mountOptions);
return { wrapper };
}

View file

@ -0,0 +1,172 @@
<template>
<div v-for="(q, i) in questions" :key="i" class="questionBlock" >
<transition appear name="fade" mode="out-in">
<buttonQuestion
v-if="q.answerSelected || q.questionSequence === currentQuestionNum"
class="radioQuestion"
:class="q.questionSequence === currentQuestionNum && 'current-question'"
:questionText="q.questionText"
:answers="q.answers"
:groupName="`question-${index}-${q.questionSequence}`"
:modelValue="q.answerSelected"
@update:modelValue="handleAnswer(q, $event)"
isRequired
:validationRules="validationRules" />
</transition>
</div>
</template>
<script>
import buttonQuestion from "@/digital-components/button-question/button-question";
import { useValidateForm } from "vee-validate";
export default {
name: "questionChain",
data() {
return {
currentQuestionNum: 0,
questions: [],
};
},
props: {
questionData: Array,
validationRules: String,
modelValue: Object,
index: Number,
answerKey: String,
},
async created() {
// do a test validation check upon create to prevent out of sync / incorrect valid states
await useValidateForm(); // NOTE: needs to have async/await here; tested and won't work without it
this.questionData.map((q, i) => {
const question = {
questionText: q.questionText,
questionSequence: q.questionSequence,
answers: q.answers.map((a) => {
return {
buttonLabel: a.answerText,
// Name will either be nextQuestionSequence or answerResult
// Name will be used by list-button as the input value.
// It must be a single string or number, so concatenating together a string with
// 4 pieces of data separated by pipe characters:
// question number|type of answer|answer value|answer text
value: a.nextQuestionSequence
? q.questionSequence +
"|nextQuestion|" +
a.nextQuestionSequence +
"|" +
a.answerText
: q.questionSequence + "|answer|" + a.answerResult + "|" + a.answerText,
nextQuestionSequence: a.nextQuestionSequence,
answerResult: a.answerResult,
questionSequence: q.questionSequence,
questionType: a.nextQuestionSequence ? "nextQuestion" : "answer",
};
}),
answerSelected: q.answerSelected || "",
};
if (!q.suppressThisQuestion) {
this.questions.push(question);
}
});
if (!this.modelValue?.length > 0 && this.questions.length > 0) {
// set this.currentQuestionNum to first valid question
this.currentQuestionNum = this.questions[0].questionSequence;
// scroll the next question into view
this.$nextTick(() => {
document.querySelector(".current-question")?.scrollIntoView({ behavior: "smooth" });
});
}
},
methods: {
handleAnswer(question, returnedAnswer) {
/*
returnedAnswer example format:
"1|answer|DD11132|Yes"
*/
question.answerSelected = returnedAnswer;
const isQuestionChainComplete = this.getQuestionChainAnswerIfComplete(returnedAnswer);
if (isQuestionChainComplete) {
this.$emit("update:modelValue", isQuestionChainComplete);
}
},
getQuestionChainAnswerIfComplete(returnedAnswer) {
// this method will return either a final answer or Boolean false
if (!returnedAnswer) {
return false;
}
// Example returnedAnswers:
// "1|nextQuestion|3|No"
// "5|answer|DW02104|Yes"
const returnedAnswerArray = returnedAnswer.split("|");
const questionNum = parseInt(returnedAnswerArray[0]);
const questionType = returnedAnswerArray[1];
const questionAnswer = returnedAnswerArray[2];
const questionAnswerText = returnedAnswerArray[3];
const answeredQuestions = [];
this.questions.forEach((q) => {
// find this question and mark it as "answered" by populating answerSelected
if (q.questionSequence === questionNum) {
q.answerSelected = returnedAnswer;
q.answerNumber = questionNum;
q.selectedAnswerText = questionAnswerText;
}
// remove all answers AFTER this question...
// (needed in case user is changing previously answered questions)
if (q.questionSequence > questionNum) {
delete q.answerSelected;
}
if (q.answerSelected) {
answeredQuestions.push({
questionText: q.questionText,
selectedAnswerText: q.answerSelected.split("|")[3],
questionNum: q.questionSequence,
});
}
});
// return false if there's a nextQuestion... or return an object with final answers (truthy)
if (questionType === "nextQuestion") {
// update to next question index
this.currentQuestionNum = parseInt(questionAnswer); // update count to display next question
// scroll the next question into view
this.$nextTick(() => {
document
.querySelector(".current-question")
.scrollIntoView({ behavior: "smooth" });
});
return false;
} else {
// reset current question index (removes .current-question class)
this.currentQuestionNum = 0; // reset count
// return an object with the part answer, all the answered questions, and the part index
return {
answerResult: questionAnswer,
answeredQuestions: answeredQuestions,
index: this.index,
};
}
},
},
components: {
buttonQuestion,
},
};
</script>
<style lang="scss">
.questionBlock:not(:nth-of-type(1)){
.button-question.radioQuestion >div {
margin-top: 0.73rem;
}
}
</style>

View file

@ -0,0 +1,59 @@
import { shallowMount } from "@vue/test-utils";
import TextBlock from "./text-block";
describe("modal.vue", () => {
it("Should display 'Text' when 'Text' is defined in the CMS", async () => {
// Act
const wrapper = shallowMount(TextBlock, {
mixins: [mockMixin],
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Text"]));
});
it("Should contain the typeStyle class as defined by the prop", async () => {
// Act
const wrapper = shallowMount(TextBlock, {
mixins: [mockMixin],
propsData: mockProps,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockProps["typeStyle"]));
});
it("Should contain the justifyText class as defined by the prop", async () => {
// Act
const wrapper = shallowMount(TextBlock, {
mixins: [mockMixin],
propsData: mockProps,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockProps["justifyText"]));
});
it("Should contain the fontWeight class as defined by the prop", async () => {
// Act
const wrapper = shallowMount(TextBlock, {
mixins: [mockMixin],
propsData: mockProps,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockProps["fontWeight"]));
});
});
///////////////
// Constants //
///////////////
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return mockCmsContent[cmsFieldName];
}),
},
};
const mockProps = {
fontWeight: "mockFontWeight",
typeStyle: "mockTypeStyle",
justifyText: "mockJustifyText",
};
const mockCmsContent = {
Text: "Sample text here.",
};

View file

@ -0,0 +1,43 @@
<template>
<div
class="text-block w-100"
:class="[justifyText, typeStyle, fontWeight]"
v-html="this.TextBlockCopy"></div>
</template>
<script>
export default {
name: "textBlock",
props: {
justifyText: String, // left, right, center
typeStyle: String, // h1-h6, body, small, label, caption (see Figma or Confluence documentation)
fontWeight: String, // bold=500, default is 400
cmsWidgetName: String,
},
computed: {
TextBlockCopy() {
return this.getCmsContent(this.cmsWidgetName, "Text");
},
},
};
</script>
<style lang="scss" scoped>
.text-block {
&.left {
display: flex;
justify-content: flex-start;
}
&.right {
display: flex;
justify-content: flex-end;
}
&.center {
display: flex;
justify-content: center;
}
&.bold {
font-weight: 500;
}
}
</style>

View file

@ -0,0 +1,170 @@
import { shallowMount } from "@vue/test-utils";
import textboxQuestion from "./textbox-question";
// Mock CMS content
const questionText = "Question Text";
const mockMixin = {
methods: {
getCmsContent: jest.fn().mockImplementation(()=> {
return questionText;
})
}
}
const maska = jest.fn();
describe("textboxQuestion.vue", () => {
it("Should render a text input", async () => {
// Arrange
const wrapper = shallowMount(textboxQuestion, {
global: {
directives: {
maska: maska,
}
},
mixins: [mockMixin]
});
wrapper.getCmsContent = jest.fn();
// Act
const input = wrapper.find("input");
// Assert
expect(input.exists()).toBe(true);
});
it("Should render the 'questionText' data value as the label text when disableAutoFill is false.", async () => {
// Arrange
const wrapper = shallowMount(textboxQuestion, {
global: {
directives: {
maska: maska,
}
},
mixins: [mockMixin]
});
// Act
const label = wrapper.find("label");
// Assert
expect(label.text()).toContain(questionText);
});
it("Should return input id as the id of the input field", async () => {
// Arrange
const wrapper = shallowMount(textboxQuestion, {
global: {
directives: {
maska: maska,
}
},
propsData: {
inputId: "input ID",
},
mixins: [mockMixin]
});
// Act
const input = wrapper.find("input");
// Assert
expect(input.attributes().id).toEqual("input ID");
});
it("Should render the 'questionText' data value as the aria-label attribute.", async () => {
// Arrange
const wrapper = shallowMount(textboxQuestion, {
global: {
directives: {
maska: maska,
}
},
mixins: [mockMixin]
});
// Act
const label = wrapper.find("label");
// Assert
expect(label.attributes("aria-label")).toContain(questionText);
});
it("Should return aria-disabled state as disabled", async () => {
// Arrange
const wrapper = shallowMount(textboxQuestion, {
global: {
directives: {
maska: maska,
}
},
propsData: {
isDisabled: true,
},
mixins: [mockMixin]
});
// Assert
const input = wrapper.find("input");
// Expect
expect(input.attributes("aria-disabled")).toEqual("true");
});
it("Should emit new value when modelValue is changed", async () => {
// Act
const wrapper = shallowMount(textboxQuestion, {
global: {
directives: {
maska: maska,
}
},
propsData: {
modelValue: "val",
},
mixins: [mockMixin]
});
await wrapper.find("input").setValue("val2");
// Assert
expect(wrapper.emitted()).toHaveProperty('change')
});
it("Should call this.handleChange with new value when the value is changed and the new value is valid", async () => {
// Arrange
const wrapper = shallowMount(textboxQuestion, {
global: {
directives: {
maska: maska,
}
},
propsData: {
options: {},
modelValue: "foo",
},
mixins: [mockMixin]
});
wrapper.vm.handleChange = jest.fn().mockImplementation(() => {});
wrapper.vm.validate = jest.fn().mockImplementation(() => {
return true;
});
// Act
wrapper.vm.$options.watch.value.call(wrapper.vm, "bar");
// Assert
expect(wrapper.vm.handleChange).toHaveBeenCalled;
});
});

View file

@ -0,0 +1,325 @@
<template>
<div class="textbox-question" :class="(errors && errors.length) || hasError ? 'has-error' : ''">
<label
v-if="displayQuestionText"
:for="inputId"
:aria-label="questionText"
class="form-label"
:class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']"
v-html="labelText"></label>
<div
class="input-wrapper"
:class="[
includeSearchIcon ? 'has-search-icon' : '',
includeSelectIcon ? 'has-select-icon' : '',
]">
<input
class="form-control"
v-model.trim.lazy="value"
v-maska="mask"
:type="type"
:ref="inputId"
:id="inputId"
:name="inputId"
:placeholder="placeholderText"
:aria-disabled="isDisabled"
:disabled="isDisabled"
:aria-required="isRequired"
:aria-label="questionText"
:min="min"
:max="max"
required
:class="[
hasIcon ? 'has-icon' : '',
iconRight ? 'icon-right' : '',
cornerStyle === 'rounded' ? 'rounded-pill' : '',
]"
:validationRules="validationRules"
@change="validationRules ? handleChange : () => {}"
@blur="validationRules ? handleChange : () => {}"
:maxlength="maxLength ? maxLength : '999'"
@focus="$emit('focus', $event.target.value)"
:data-bs-toggle="includeSelectIcon ? 'modal' : ''"
:data-bs-target="'#' + this.cmsWidgetName"
@paste="trimOnPaste"
@drop="trimOnPaste"/>
<button v-if="includeSearchIcon" type="submit" aria-label="Search button" />
<button v-if="includeSelectIcon" type="submit" data-bs-toggle="modal"
:data-bs-target="'#' + this.cmsWidgetName" aria-label="Select button" />
</div>
<div v-show="errorMessage" class="row my-1 form-test-error">
<span class="d-inline-flex mt-0" role="alert">{{ errorMessage }}</span>
</div>
</div>
</template>
<script>
import { useField, validate } from "vee-validate";
export default {
name: "textbox-question",
props: {
type: {
type: String,
default: "text",
},
placeholderText: {
type: String,
default: "",
},
displayQuestionText: {
type: Boolean,
default: true,
},
modelValue: String,
inputId: {
type: String,
required: true,
},
isDisabled: Boolean,
isRequired: Boolean,
hasIcon: Boolean, // If input has an icon
iconRight: Boolean, // Place icon on right side of text input, otherwise default is left if hasIcon prop is used
hasError: Boolean,
mask: {
type: String,
default: "",
},
validationRules: String,
cmsWidgetName: String,
maxLength: String,
questionAlignment: String, // Left or center. Left is default.
cornerStyle: String, // Rounded or square. Square is default.
includeSearchIcon: Boolean,
includeSelectIcon: Boolean,
min: String,
max: String,
disableAutoFill: Boolean
},
setup(props) {
const propsClone = Object.assign({}, props);
const modelValue = propsClone.modelValue;
let initialValue;
switch (typeof modelValue) {
case "number":
initialValue = modelValue;
break;
default:
initialValue = modelValue && modelValue.length > 0 ? modelValue : "";
break;
}
const fieldOptions = {
type: "text",
value: modelValue,
initialValue: initialValue,
};
const { errorMessage, handleBlur, handleChange, meta, validate, errors } = useField(
props.inputId,
props.validationRules,
fieldOptions
);
return {
errorMessage,
handleBlur,
handleChange,
validate,
meta,
errors,
};
},
methods: {
trimOnPaste(evt) {
evt.stopPropagation();
evt.preventDefault();
const data = evt.type == "paste" ? (evt.clipboardData || window.clipboardData) : evt.dataTransfer;
const value = data.getData('Text')?.trim();
this.$emit("update:modelValue", value);
},
},
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
},
value: {
get: function () {
return this.modelValue;
},
set: function (newValue) {
this.$emit("update:modelValue", newValue);
},
},
labelText: {
get: function () {
const noBreakChar = "&NoBreak;";
var questionText = "";
if (this.disableAutoFill) {
var words = this.questionText.toString().split(/[ ]+/);
words.forEach(function (word) {
const position = 1;
word = [
word.toString().slice(0, position),
noBreakChar,
word.toString().slice(position),
].join("");
questionText += `${word} `;
});
questionText = questionText.trimEnd();
} else {
questionText = this.questionText.toString();
}
return questionText;
},
},
},
mounted() {
this.$emit("textboxQuestionEvent.inputIdAssigned", this.inputId);
},
watch: {
async value(newValue) {
const result = await validate(newValue, this.validationRules); // do a test validation check, without triggering full validation
if (result.valid) {
this.handleChange(newValue); // trigger full validation on this field only
}
},
},
};
</script>
<style lang="scss">
input[type="date"]::-webkit-inner-spin-button{
display: none;
}
input[type="date"]::-webkit-calendar-picker-indicator {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 1px;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.1762 1.52764H13.7656V1.50352C13.7656 1.10476 13.6076 0.722334 13.3263 0.44037C13.0451 0.158406 12.6636 0 12.2659 0C11.8681 0 11.4866 0.158406 11.2054 0.44037C10.9241 0.722334 10.7661 1.10476 10.7661 1.50352V1.52764H5.42476V1.50352C5.42476 1.10476 5.26675 0.722334 4.9855 0.44037C4.70424 0.158406 4.32277 0 3.92501 0C3.52725 0 3.14579 0.158406 2.86453 0.44037C2.58327 0.722334 2.42526 1.10476 2.42526 1.50352V1.52764H1.82376C1.34046 1.52891 0.877316 1.72195 0.53557 2.06455C0.193824 2.40716 0.00127018 2.87146 0 3.35598V14.1717C0.0016909 14.656 0.194379 15.1201 0.536035 15.4626C0.87769 15.8051 1.34059 15.9983 1.82376 16H14.1746C14.6581 15.9987 15.1214 15.8057 15.4634 15.4632C15.8054 15.1206 15.9983 14.6563 16 14.1717V3.35598C15.9987 2.87146 15.8062 2.40716 15.4644 2.06455C15.1227 1.72195 14.6595 1.52891 14.1762 1.52764ZM11.8889 1.50352C11.8889 1.4033 11.9286 1.30718 11.9993 1.23631C12.07 1.16544 12.1659 1.12563 12.2659 1.12563C12.3658 1.12563 12.4617 1.16544 12.5324 1.23631C12.6031 1.30718 12.6428 1.4033 12.6428 1.50352V2.99899C12.6428 3.09922 12.6031 3.19534 12.5324 3.2662C12.4617 3.33707 12.3658 3.37688 12.2659 3.37688C12.1659 3.37688 12.07 3.33707 11.9993 3.2662C11.9286 3.19534 11.8889 3.09922 11.8889 2.99899V1.50352ZM3.54807 1.50352C3.54807 1.4033 3.58778 1.30718 3.65847 1.23631C3.72916 1.16544 3.82504 1.12563 3.92501 1.12563C4.02498 1.12563 4.12086 1.16544 4.19155 1.23631C4.26224 1.30718 4.30195 1.4033 4.30195 1.50352V2.99899C4.30195 3.09922 4.26224 3.19534 4.19155 3.2662C4.12086 3.33707 4.02498 3.37688 3.92501 3.37688C3.82504 3.37688 3.72916 3.33707 3.65847 3.2662C3.58778 3.19534 3.54807 3.09922 3.54807 2.99899V1.50352ZM14.8772 14.1717C14.8747 14.3573 14.8001 14.5345 14.6691 14.6658C14.5382 14.797 14.3614 14.8719 14.1762 14.8744H1.82536C1.63995 14.8723 1.4627 14.7976 1.33144 14.6663C1.20018 14.5351 1.12531 14.3575 1.12281 14.1717V6.59296H14.8772V14.1717Z' fill='%23167CAC'/%3E%3Cpath d='M2.33063 11.282H3.93464V12.6006C3.93464 12.7499 3.99379 12.8931 4.09907 12.9986C4.20435 13.1041 4.34715 13.1634 4.49604 13.1634C4.64494 13.1634 4.78773 13.1041 4.89301 12.9986C4.9983 12.8931 5.05745 12.7499 5.05745 12.6006V11.282H7.46346V12.6006C7.46346 12.7499 7.52261 12.8931 7.62789 12.9986C7.73318 13.1041 7.87597 13.1634 8.02486 13.1634C8.17376 13.1634 8.31655 13.1041 8.42184 12.9986C8.52712 12.8931 8.58627 12.7499 8.58627 12.6006V11.282H10.9923V12.6006C10.9923 12.7499 11.0514 12.8931 11.1567 12.9986C11.262 13.1041 11.4048 13.1634 11.5537 13.1634C11.7026 13.1634 11.8454 13.1041 11.9507 12.9986C12.0559 12.8931 12.1151 12.7499 12.1151 12.6006V11.282H13.7191C13.868 11.282 14.0108 11.2227 14.1161 11.1172C14.2214 11.0116 14.2805 10.8685 14.2805 10.7192C14.2805 10.57 14.2214 10.4268 14.1161 10.3213C14.0108 10.2157 13.868 10.1564 13.7191 10.1564H12.1151V8.84425C12.1151 8.69498 12.0559 8.55183 11.9507 8.44628C11.8454 8.34073 11.7026 8.28143 11.5537 8.28143C11.4048 8.28143 11.262 8.34073 11.1567 8.44628C11.0514 8.55183 10.9923 8.69498 10.9923 8.84425V10.1628H8.58627V8.84425C8.58627 8.69498 8.52712 8.55183 8.42184 8.44628C8.31655 8.34073 8.17376 8.28143 8.02486 8.28143C7.87597 8.28143 7.73318 8.34073 7.62789 8.44628C7.52261 8.55183 7.46346 8.69498 7.46346 8.84425V10.1628H5.05745V8.84425C5.05745 8.69498 4.9983 8.55183 4.89301 8.44628C4.78773 8.34073 4.64494 8.28143 4.49604 8.28143C4.34715 8.28143 4.20435 8.34073 4.09907 8.44628C3.99379 8.55183 3.93464 8.69498 3.93464 8.84425V10.1628H2.33063C2.18174 10.1628 2.03894 10.2221 1.93366 10.3277C1.82837 10.4332 1.76923 10.5764 1.76923 10.7257C1.76923 10.8749 1.82837 11.0181 1.93366 11.1236C2.03894 11.2292 2.18174 11.2885 2.33063 11.2885V11.282Z' fill='%23167CAC'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
width: 16px; // check this
height: 16px;
display: flex;
border-radius: 0 7px 7px 0;
background-color: #E4F1F7;
&:hover {
cursor: pointer;
}
padding: 15px 0;
min-width: 48px;
}
.textbox-question {
label {
color: $black;
font-weight: 500;
}
span{
font-weight:400;
font-size:14px;
color: #4D5151;
}
.form-test-error span{
color: #d4281c;
font-size: 0.875rem;
font-weight: 500;
}
.input-wrapper {
position: relative;
&.has-search-icon {
input[type="text"] {
border-radius: 50rem;
}
button[type="submit"] {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 0;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.7817 14.7328L11.8252 10.7762C12.8833 9.45005 13.3936 7.76911 13.2513 6.07849C13.1091 4.38788 12.325 2.81587 11.0601 1.6852C9.79515 0.554524 8.14538 -0.0490261 6.44946 -0.00154744C4.75353 0.0459312 3.14012 0.740836 1.94045 1.94051C0.740775 3.14018 0.0458701 4.75359 -0.00160848 6.44952C-0.0490871 8.14545 0.554463 9.79521 1.68514 11.0601C2.81581 12.325 4.38782 13.1091 6.07843 13.2514C7.76905 13.3937 9.44999 12.8834 10.7762 11.8252L14.7349 15.7839C14.8044 15.8527 14.8869 15.907 14.9774 15.9439C15.068 15.9808 15.165 15.9995 15.2628 15.9989C15.3606 15.9983 15.4573 15.9784 15.5475 15.9405C15.6376 15.9025 15.7194 15.8471 15.7881 15.7776C15.8568 15.708 15.9112 15.6256 15.9481 15.535C15.985 15.4444 16.0036 15.3474 16.0031 15.2496C16.0025 15.1518 15.9826 15.0551 15.9446 14.965C15.9067 14.8748 15.8513 14.7931 15.7817 14.7243V14.7328ZM6.63737 11.7913C5.61803 11.7913 4.62157 11.4891 3.77402 10.9228C2.92646 10.3564 2.26587 9.5515 1.87578 8.60975C1.4857 7.668 1.38363 6.63172 1.5825 5.63196C1.78136 4.6322 2.27222 3.71386 2.99301 2.99307C3.7138 2.27229 4.63214 1.78142 5.6319 1.58256C6.63166 1.38369 7.66793 1.48576 8.60969 1.87585C9.55144 2.26593 10.3564 2.92652 10.9227 3.77408C11.489 4.62163 11.7913 5.61809 11.7913 6.63743C11.7896 8.00382 11.2461 9.31376 10.2799 10.2799C9.3137 11.2461 8.00376 11.7897 6.63737 11.7913Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
background-repeat: no-repeat;
background-position: center;
border-radius: 0 50rem 50rem 0;
background-color: $blue-100;
width: 2.75rem;
height: 100%;
border: 1px solid $gray-500;
border-left: none;
display: flex;
}
}
&.has-select-icon {
button[type="submit"] {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 1rem;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center;
background-color: transparent;
width: 1rem;
height: 100%;
border: 0px;
border-left: none;
display: flex;
}
}
}
input {
&.has-icon {
background-image: url(~@/assets/img/icons/location-pin.svg);
background-repeat: no-repeat;
background-position: 0.75rem 50%;
background-size: 1rem auto;
padding: 0 0.75rem 0 2.5rem;
&.icon-right {
background-position: calc(100% - 0.75rem) 50%;
padding: 0 2.5rem 0 0.75rem;
}
}
}
.form-label {
margin-bottom: 0.25rem;
}
.form-control {
border: 1px solid $gray-500;
border-radius: 0.5rem;
min-height: 3rem;
max-height: 48px;
padding: 12px 16px;
&::placeholder {
color: $gray-500;
}
&:focus {
box-shadow: 0 0 0 2.5px $blue;
}
&:disabled,
&.disabled {
background-color: $gray-100;
&:hover {
box-shadow: 0 0 0 4px transparent;
border: 1px solid $gray-500;
}
}
&:hover {
border: 1px solid $gray-500;
box-shadow: 0 0 0 4px $blue-300;
}
}
p {
display: none;
}
}
</style>

73
src/global-methods.js Normal file
View file

@ -0,0 +1,73 @@
import axios from "axios";
import analyticsMixIn from "@/mixins/analytics-mixin.js";
import { useMainStore } from "@/store";
import { applicationConfig } from "@/constants/application-config.js";
import { GaCategories, GaActions, GaLabels } from "@/constants/analytics";
import { headerKeys } from "@/constants/header-keys";
export default {
callHttpClient({ method, endpoint, payload, logApiCall = true}) {
return new Promise((resolve, reject) => {
const store = useMainStore();
const cfDistroUrl = applicationConfig.CONSUMER_CF_DISTRO;
const payloadAndAnalyticsData = Object.assign({}, payload, { AppName: "SelfService" });
const headers = {
[headerKeys.EXPERIMENT]: JSON.stringify(store.experimentSettings),
};
axios({
method: method,
url: cfDistroUrl + endpoint,
data: payloadAndAnalyticsData,
crossDomain: true,
responseType: 'json',
headers: headers,
})
.then((response) => {
if (logApiCall) {
analyticsMixIn.methods.pushEventToGA(
GaCategories.API_RESPONSE,
GaActions.RESULT,
`${GaLabels.SUCCESS}_${endpoint}`,
true
);
}
return resolve(response);
},
error => {
console.error(error);
//implement if analytics service is down
if(endpoint.includes("analytics")){
return resolve({data: ""});
}
return reject(error.response);
}
);
});
},
//used for mocked services
async mockCallHttpClient(method, endpoint) {
return new Promise((resolve, reject) => {
axios({
method: method,
url: endpoint,
crossDomain: true,
responseType: {}
})
.then((response) => {
return resolve(response);
},
error => {
console.error(error);
return reject(error.response);
}
);
});
}
};

Some files were not shown because too many files have changed in this diff Show more