Merge branch 'develop' into feature/jnou/fix-iss-regression

This commit is contained in:
JennyNou 2026-03-19 10:59:40 -04:00
commit b827ec8f45
102 changed files with 3840 additions and 1668 deletions

326
package-lock.json generated
View file

@ -8,6 +8,7 @@
"name": "digitalconsumer.iss",
"version": "0.1.0",
"dependencies": {
"@adyen/adyen-web": "^6.31.1",
"axios": "^1.13.5",
"axios-retry": "^3.5.0",
"bootstrap": "^5.3",
@ -93,6 +94,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/@adyen/adyen-web": {
"version": "6.31.1",
"resolved": "https://registry.npmjs.org/@adyen/adyen-web/-/adyen-web-6.31.1.tgz",
"integrity": "sha512-vxeJW1bTvjXxYf6CkPQV8gbYIhm/hupcjjeoKkkBdxk7geW3qy18Jlco4/MuSeb88ctTRM3gebwFXpqF2NjBLQ==",
"license": "MIT",
"dependencies": {
"@types/applepayjs": "14.0.9",
"@types/googlepay": "0.7.10",
"classnames": "2.5.1",
"preact": "10.28.2"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"dev": true,
@ -3298,9 +3311,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
"integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"cpu": [
"arm"
],
@ -3312,9 +3325,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
"integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"cpu": [
"arm64"
],
@ -3326,9 +3339,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
"integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"cpu": [
"arm64"
],
@ -3340,9 +3353,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
"integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"cpu": [
"x64"
],
@ -3354,9 +3367,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
"integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"cpu": [
"arm64"
],
@ -3368,9 +3381,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
"integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"cpu": [
"x64"
],
@ -3382,9 +3395,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
"integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"cpu": [
"arm"
],
@ -3396,9 +3409,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
"integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"cpu": [
"arm"
],
@ -3410,9 +3423,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
"integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [
"arm64"
],
@ -3424,9 +3437,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
"integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"cpu": [
"arm64"
],
@ -3438,9 +3451,23 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
"integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [
"loong64"
],
@ -3452,9 +3479,23 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
"integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"cpu": [
"ppc64"
],
@ -3466,9 +3507,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
"integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"cpu": [
"riscv64"
],
@ -3480,9 +3521,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
"integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"cpu": [
"riscv64"
],
@ -3494,9 +3535,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
"integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"cpu": [
"s390x"
],
@ -3508,9 +3549,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"cpu": [
"x64"
],
@ -3522,9 +3563,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"cpu": [
"x64"
],
@ -3535,10 +3576,24 @@
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
"integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [
"arm64"
],
@ -3550,9 +3605,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
"integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [
"arm64"
],
@ -3564,9 +3619,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
"integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"cpu": [
"ia32"
],
@ -3578,7 +3633,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.3",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
@ -3590,7 +3647,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.3",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [
"x64"
],
@ -4079,13 +4138,11 @@
"node": ">= 10"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10.13.0"
}
"node_modules/@types/applepayjs": {
"version": "14.0.9",
"resolved": "https://registry.npmjs.org/@types/applepayjs/-/applepayjs-14.0.9.tgz",
"integrity": "sha512-xEprYbb0TEP/XIiDPbVnTYpDai8fTFpsQfVSfTd81Is2GOMUy7ie019eyX6Mz2ECxfjoUVKaiGSL577roIeHCg==",
"license": "MIT"
},
"node_modules/@types/aria-query": {
"version": "5.0.1",
@ -4245,6 +4302,12 @@
"@types/node": "*"
}
},
"node_modules/@types/googlepay": {
"version": "0.7.10",
"resolved": "https://registry.npmjs.org/@types/googlepay/-/googlepay-0.7.10.tgz",
"integrity": "sha512-ByXjDfxEp87zsjw5cVsNKEeA+AIJYcWwJvRqPu85jO0Lp/FeG67csnf4mqbPaIxmdI6+Khp3v5I0yyjq9MrGhw==",
"license": "MIT"
},
"node_modules/@types/graceful-fs": {
"version": "4.1.5",
"dev": true,
@ -4643,13 +4706,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@ -4819,11 +4882,13 @@
}
},
"node_modules/@vitest/coverage-v8/node_modules/minimatch": {
"version": "9.0.5",
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@ -7331,6 +7396,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/clean-css": {
"version": "5.3.1",
"dev": true,
@ -8872,13 +8943,15 @@
}
},
"node_modules/editorconfig": {
"version": "1.0.4",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz",
"integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@one-ini/wasm": "0.1.1",
"commander": "^10.0.0",
"minimatch": "9.0.1",
"minimatch": "^9.0.1",
"semver": "^7.5.3"
},
"bin": {
@ -8905,11 +8978,13 @@
}
},
"node_modules/editorconfig/node_modules/minimatch": {
"version": "9.0.1",
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@ -11320,7 +11395,9 @@
}
},
"node_modules/immutable": {
"version": "4.1.0",
"version": "4.3.8",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz",
"integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==",
"dev": true,
"license": "MIT"
},
@ -14615,7 +14692,9 @@
}
},
"node_modules/js-beautify/node_modules/minimatch": {
"version": "5.1.6",
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true,
"license": "ISC",
"dependencies": {
@ -15627,7 +15706,9 @@
"license": "ISC"
},
"node_modules/minimatch": {
"version": "3.1.2",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@ -17357,6 +17438,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/preact": {
"version": "10.28.2",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz",
"integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prebuild-install": {
"version": "7.1.3",
"dev": true,
@ -18027,7 +18118,9 @@
}
},
"node_modules/rollup": {
"version": "4.52.3",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -18041,28 +18134,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.3",
"@rollup/rollup-android-arm64": "4.52.3",
"@rollup/rollup-darwin-arm64": "4.52.3",
"@rollup/rollup-darwin-x64": "4.52.3",
"@rollup/rollup-freebsd-arm64": "4.52.3",
"@rollup/rollup-freebsd-x64": "4.52.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
"@rollup/rollup-linux-arm-musleabihf": "4.52.3",
"@rollup/rollup-linux-arm64-gnu": "4.52.3",
"@rollup/rollup-linux-arm64-musl": "4.52.3",
"@rollup/rollup-linux-loong64-gnu": "4.52.3",
"@rollup/rollup-linux-ppc64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-musl": "4.52.3",
"@rollup/rollup-linux-s390x-gnu": "4.52.3",
"@rollup/rollup-linux-x64-gnu": "4.52.3",
"@rollup/rollup-linux-x64-musl": "4.52.3",
"@rollup/rollup-openharmony-arm64": "4.52.3",
"@rollup/rollup-win32-arm64-msvc": "4.52.3",
"@rollup/rollup-win32-ia32-msvc": "4.52.3",
"@rollup/rollup-win32-x64-gnu": "4.52.3",
"@rollup/rollup-win32-x64-msvc": "4.52.3",
"@rollup/rollup-android-arm-eabi": "4.59.0",
"@rollup/rollup-android-arm64": "4.59.0",
"@rollup/rollup-darwin-arm64": "4.59.0",
"@rollup/rollup-darwin-x64": "4.59.0",
"@rollup/rollup-freebsd-arm64": "4.59.0",
"@rollup/rollup-freebsd-x64": "4.59.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
"@rollup/rollup-linux-arm64-musl": "4.59.0",
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
"@rollup/rollup-linux-loong64-musl": "4.59.0",
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
"@rollup/rollup-linux-x64-gnu": "4.59.0",
"@rollup/rollup-linux-x64-musl": "4.59.0",
"@rollup/rollup-openbsd-x64": "4.59.0",
"@rollup/rollup-openharmony-arm64": "4.59.0",
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
"@rollup/rollup-win32-x64-gnu": "4.59.0",
"@rollup/rollup-win32-x64-msvc": "4.59.0",
"fsevents": "~2.3.2"
}
},
@ -18248,6 +18344,16 @@
"node": ">=16.13.2"
}
},
"node_modules/sax": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz",
"integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
},
"node_modules/saxes": {
"version": "6.0.0",
"dev": true,
@ -19234,16 +19340,18 @@
"dev": true
},
"node_modules/svgo": {
"version": "2.8.0",
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.2.tgz",
"integrity": "sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@trysound/sax": "0.2.0",
"commander": "^7.2.0",
"css-select": "^4.1.3",
"css-tree": "^1.1.3",
"csso": "^4.2.0",
"picocolors": "^1.0.0",
"sax": "^1.5.0",
"stable": "^0.1.8"
},
"bin": {
@ -20056,7 +20164,9 @@
}
},
"node_modules/underscore": {
"version": "1.13.6",
"version": "1.13.8",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
"integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
"dev": true,
"license": "MIT"
},

View file

@ -20,6 +20,7 @@
"lint:inspect": "eslint --inspect-config"
},
"dependencies": {
"@adyen/adyen-web": "^6.31.1",
"axios": "^1.13.5",
"axios-retry": "^3.5.0",
"bootstrap": "^5.3",

View file

@ -3,21 +3,28 @@
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
@import url("https://fonts.googleapis.com/css2?family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap");
@font-face {
font-family: "Glyphicons Halflings";
src: url(/fonts/glyphicons-halflings-regular.eot);
src: url(/fonts/glyphicons-halflings-regular.eot?#iefix) format("embedded-opentype"), url(/fonts/glyphicons-halflings-regular.woff2) format("woff2"), url(/fonts/glyphicons-halflings-regular.woff) format("woff"), url(/fonts/glyphicons-halflings-regular.ttf) format("truetype"), url(/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format("svg");
}
body {
font-family: Urbanist, Roboto, Arial, sans-serif;
font-weight: 400;
letter-spacing: 0.03em;
color: #000;
color: #4d4e53;
margin: 0;
background-color: white;
padding: 8px;
margin-bottom: 1rem;
font-size: 1rem;
}
body .validation-info {
display: none;
color: #db0020;
}
body .italicSmall {
font-style: normal;
font-size: 14px;
font-weight: normal;
font-style: italic;
font-size: 12px;
}
body span {
line-height: 24px;
@ -25,75 +32,69 @@ body span {
body a {
color: #0070D1;
text-decoration: none;
font-weight: 500;
}
body a:hover {
text-decoration: underline;
color: #167cac;
}
body .form-group {
display: flex;
flex-direction: column;
display: block;
position: relative;
}
body .headerlinecontainer {
color: #000;
}
body .headerlinecontainer .headerline1 {
font-size: 20px;
line-height: 32px;
font-weight: 400;
margin: 24px 0;
text-align: center;
}
body input,
body select {
margin-top: 4px;
margin-bottom: 0.75rem;
margin-top: 0rem;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.375rem;
}
body .headerlinecontainer .headerline5v2 {
display: block;
padding: 0.75rem 0;
color: #db0020;
font-style: italic;
font-size: 12px;
}
body input,
body select {
height: 2.8125rem;
border: solid 1px #b3b4b5;
border-radius: 4px;
border: 1px solid #8E9292;
padding: 16px;
font-size: 16px;
box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2);
color: #525656;
font-family: inherit;
letter-spacing: inherit;
color: #000;
background-color: #fff;
font-weight: normal;
font-style: normal;
font-stretch: normal;
font-size: 16px;
line-height: 1.56;
padding: 10px;
width: 100%;
}
body input:focus, body input:focus-visible,
body select:focus,
body select:focus-visible {
box-shadow: 0 0 0 2.5px #1574a1;
border: 1px solid transparent;
border: solid 2.5px #0070d1;
outline: none;
}
body input:disabled, body input.disabled,
body select:disabled,
body select.disabled {
background-color: #f5f5f5;
filter: grayscale(100%);
}
body input:disabled:hover, body input.disabled:hover,
body select:disabled:hover,
body select.disabled:hover {
box-shadow: 0 0 0 4px transparent;
border: 1px solid #8e9292;
}
body input:hover,
body select:hover {
border: 1px solid #8e9292;
box-shadow: 0 0 0 4px #9fcee6;
}
body select {
border-radius: 4px;
border: 1px solid #8E9292;
padding: 16px;
width: 100%;
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: 95% 50%;
background-size: 15px 9px;
background: url(/icons/select.png) no-repeat 98% #ffffff;
background-size: 10px 17px;
appearance: none;
font-size: 16px;
color: #000;
}
body .creditCardSpecific div {
padding: 8px 0;
}
body .creditCardSpecific div .headerlinecontainer {
display: none;
body h3 {
font-size: 1rem;
font-weight: 600;
margin-top: 1.375rem;
margin-bottom: 0.6875rem;
color: #000;
}
body .creditCardSpecific div label {
font-weight: 500;
@ -124,87 +125,84 @@ body #cardExpirationContainer {
body .optional-label span {
display: none;
}
body .optional-label:after {
content: " (optional)";
font-weight: 300;
font-style: normal;
font-size: 15px;
body .buttonContainer #sghopcancelbutton {
float: left;
}
body .buttonContainer {
height: 48px;
body .buttonContainer #sghopsubmitbutton {
float: right;
}
body .buttonContainer button {
position: relative;
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
border: none;
border-radius: 8px;
color: #fff;
justify-content: center;
font-weight: 500;
font-size: 16px;
font-family: inherit;
letter-spacing: inherit;
height: 48px;
body .buttonContainer #sghopsubmitbutton.creditCardSpecific {
float: right;
}
@media (hover: hover) {
body .buttonContainer button {
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
cursor: pointer;
@media (max-width: 500px) {
body .buttonContainer #sghopsubmitbutton.creditCardSpecific {
float: left;
width: 100%;
}
}
body .buttonContainer button:focus {
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
body .buttonContainer #selectPaymentTypeButton {
float: right;
}
body .buttonContainer button:focus, body .buttonContainer button:focus-visible {
outline: none;
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
color: #fff;
background: linear-gradient(270deg, rgb(6, 87, 124) 0%, rgb(6, 87, 124) 100%);
@media (max-width: 500px) {
body .buttonContainer #selectPaymentTypeButton {
float: left;
width: 100%;
}
}
body .buttonContainer button:disabled {
background: #e3e4e4 !important;
background: linear-gradient(270deg, #e3e4e4 0%, #e3e4e4 100%) !important;
color: #4d5151 !important;
font-weight: 400;
height: 48px;
body .buttonContainer .btn-success {
width: 99%;
height: 45px;
border: none;
border-radius: 8px;
border-radius: 22.5px;
font-size: 16px;
line-height: 0.94;
font-weight: 600;
color: #ffffff;
background-color: #0c7e47;
border-color: #0a673a;
font-family: Urbanist;
cursor: pointer;
pointer-events: all;
}
body .buttonContainer button.has-loader {
color: #fff;
background: #06577c;
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
pointer-events: auto;
body .buttonContainer .btn-success:hover {
background-color: #084f2d;
}
body .buttonContainer button.delay {
transition: background 0s 0s ease-in-out;
body .buttonContainer .btn-success:focus, body .buttonContainer .btn-success:active {
outline: none;
background-color: #0c7e47;
box-shadow: 0 0 0 3px #ffffff, 0 0 0 5.5px #0c7e47;
}
body .has-error input,
body .has-error select {
border: 1px solid #d4281c;
body .has-error input.form-control,
body .has-error select.form-control {
border-color: #db0020;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
body .has-error input:focus,
body .has-error select:focus {
box-shadow: 0 0 0 2.5px #fcbfbb;
}
body .has-error input:hover,
body .has-error select:hover {
border: 1px solid #d4281c;
box-shadow: 0 0 0 4px #fcbfbb;
body .has-error input.form-control:focus,
body .has-error select.form-control:focus {
box-shadow: none;
}
body .has-error select {
border: 1px solid #d4281c;
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='%23d4281c'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: 95% 50%;
background-size: 15px 9px;
appearance: none;
background-image: url(/icons/select-error.png);
}
body .has-error small {
margin-top: 4px;
color: #d4281c;
font-size: 1rem;
line-height: 1.56;
margin-bottom: 0.3125rem;
color: #db0020;
}
body .has-success .control-label {
color: #4d4e53;
}
body .has-success input.form-control,
body .has-success select.form-control {
border-color: #0c7e47;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
body .has-success input.form-control:focus,
body .has-success select.form-control:focus {
box-shadow: none;
}
body .has-success select {
background-image: url(/icons/select-success.png);
}
body .cc-error-container #alert-message {
background-color: #fcbfbb;
@ -212,14 +210,17 @@ body .cc-error-container #alert-message {
}
#fmg-checkout-shared .form-group .list-group.radio .list-group-item {
border-radius: 10px;
border-radius: 5px;
}
#fmg-checkout-shared .form-group .list-group.radio .list-group-item.active {
box-shadow: 0 0 0 5px #b9d7e6;
}
#fmg-checkout-shared .form-group .list-group.radio .list-group-item label {
padding: 12px 20px;
}
#fmg-checkout-shared #applepay-and-paypal-container {
display: inline-flex;
width: 100%;
width: 80%;
justify-content: space-between;
}
#fmg-checkout-shared #applepay-and-paypal-container #paypalParentDiv {
@ -227,38 +228,31 @@ body .cc-error-container #alert-message {
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group {
margin: 0;
width: 100%;
width: 48%;
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group.paymentSelection #applepayParentLink {
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group.paymentSelection .list-group-item {
border-radius: 35px;
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group.paymentSelection .list-group-item#applepayParentLink {
margin: 0;
padding: 10px 0;
background-color: #000000;
border: none;
box-shadow: none;
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group.paymentSelection #applepayParentLink label {
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group.paymentSelection .list-group-item#applepayParentLink label {
padding: 0;
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group.paymentSelection #applepayParentLink label apple-pay-button {
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group.paymentSelection .list-group-item#applepayParentLink label apple-pay-button {
width: 100%;
vertical-align: middle;
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group.paymentSelection #applepayParentLink.list-group-item {
box-shadow: none;
border-radius: 10px;
border: 1px solid #8E9292;
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group.paymentSelection #applepayParentLink:host {
--apple-pay-button-width: auto;
--apple-pay-button-height: 47px;
--apple-pay-button-border-radius: 35px;
--apple-pay-button-padding: 2px 0;
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group #paypalParentLink {
margin: 0;
padding: 2px 0;
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group #paypalParentLink #paypalImage {
max-width: 50%;
}
#fmg-checkout-shared #applepay-and-paypal-container .form-group .list-group #paypalParentLink.list-group-item {
box-shadow: none;
border-radius: 10px;
border: 1px solid #8E9292;
max-width: 90px;
}
#fmg-checkout-shared #applepay-and-paypal-container.no-applepay .form-group {
width: 100%;
@ -273,15 +267,19 @@ body .cc-error-container #alert-message {
width: 100%;
}
#fmg-checkout-shared #applepay-and-paypal-container.no-applepay .form-group .list-group #paypalParentLink #paypalImage {
max-width: 25%;
max-width: 5.3125rem;
}
#fmg-checkout-shared #other-payments-container label {
display: inline-flex;
display: block;
text-align: left;
align-items: flex-start;
justify-content: space-between;
font-size: 16px;
font-weight: 500;
font-weight: 600;
line-height: 1.56;
}
#fmg-checkout-shared #other-payments-container label.control-label {
justify-content: start;
}
#fmg-checkout-shared #other-payments-container label span.paymentOptionText {
margin: auto 0;
@ -294,35 +292,56 @@ body .cc-error-container #alert-message {
#fmg-checkout-shared #other-payments-container #afterpayParentLink label > span:last-of-type {
width: 0;
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv #creditCardParentLink {
margin-bottom: 0;
}
@media (max-width: 33.9em) {
#fmg-checkout-shared #other-payments-container #creditCardParentDiv #creditCardParentLink {
min-height: 3.5rem;
}
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv #creditCardParentLink label {
display: flex;
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv #creditCardParentLink label .paymentOptionText {
text-align: left;
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv.active {
margin-bottom: 32px;
padding-bottom: 10px;
border: 1px solid #0070D1;
border-radius: 10px;
border-radius: 5px;
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv.active > .paymentSelection #creditCardParentLink {
border-width: 0 0 2px 0;
min-height: unset;
border-bottom-color: #0070D1;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv .paymentSelection #creditCardImages {
display: flex;
flex-direction: row;
width: 50%;
text-align: right;
justify-content: flex-end;
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv .paymentSelection #creditCardImages > img {
width: 20%;
#fmg-checkout-shared #other-payments-container #creditCardParentDiv .paymentSelection #creditCardImages img {
max-width: fit-content;
width: 25%;
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv .paymentSelection #creditCardImages img:not(:last-of-type) {
margin-right: 1%;
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv .creditCardSpecific {
padding: 0 16px;
padding: 0 2%;
}
#fmg-checkout-shared #other-payments-container #creditCardParentDiv .creditCardSpecific > .form-group {
width: 100%;
margin-bottom: 0.5rem;
}
#fmg-checkout-shared #other-payments-container .italicSmall {
margin-top: 10px;
}
#fmg-checkout-shared #other-payments-container .disclaimerContainerv2 {
margin-bottom: 32px;
margin: 1rem 0;
}
@media only screen and (max-device-width: 767px) {
#fmg-checkout-shared #applepay-and-paypal-container {
@ -332,3 +351,26 @@ body .cc-error-container #alert-message {
width: 100%;
}
}
.form-control-feedback {
position: absolute;
top: 0;
right: 0;
z-index: 2;
display: block;
width: 38px;
height: 38px;
line-height: 38px;
text-align: center;
pointer-events: none;
top: 27px;
}
.form-control-feedback.glyphicon {
display: none;
}
.form-control-feedback.glyphicon-remove:before {
content: "\e014";
}
.form-control-feedback.glyphicon-ok:before {
content: "\e013";
}

View file

@ -4,103 +4,16 @@
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* Style the modal when PayPal button is selected */
#pageLoadingModal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
}
#pageLoadingModal .modal-content {
margin: auto auto;
}
#pageLoadingModal p {
margin: 0;
text-transform: uppercase;
font-size: 14px;
}
#pageLoadingModal .modal {
display: none;
overflow: hidden;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
-webkit-overflow-scrolling: touch;
outline: 0;
}
#pageLoadingModal .modal-content {
position: relative;
background-color: #fff;
border-radius: 8px;
background-clip: padding-box;
outline: 0;
width: 176px;
height: 136px;
display: flex;
justify-content: center;
align-items: center;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center center;
background-size: 96px;
}
#pageLoadingModal .modal-content:after {
content: "";
border-radius: 50%;
position: absolute;
width: 5.5rem;
height: 5.5rem;
border: 0.45em solid #d4281c;
border-right-color: transparent;
animation: spinnerrotate 0.75s linear infinite;
}
@keyframes spinnerrotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
#pageLoadingModal .modal-content p {
display: none;
}
#pageLoadingModal .modal-backdrop {
position: absolute;
top: 0;
right: 0;
left: 0;
background-color: #000000;
}
#pageLoadingModal .modal-backdrop.fade {
opacity: 0;
filter: alpha(opacity=0);
}
#pageLoadingModal.hide {
display: none;
}
.buy-backdrop {
background-color: rgba(0, 0, 0, 0.3) !important;
}
.buy-backdrop #afterpay__iframe-checkout-container {
width: 100%;
background-color: rgba(0, 0, 0, 0.8) !important;
}
.buy-backdrop #afterpay__iframe-checkout-container .style_closeWrapper__lIHJe {
display: none;
}
.buy-container-closer {
display: none;
}
@keyframes rotate {
0% {
transform: rotate(0);

Binary file not shown.

View file

@ -0,0 +1,229 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
<font-face units-per-em="1200" ascent="960" descent="-240" />
<missing-glyph horiz-adv-x="500" />
<glyph />
<glyph />
<glyph unicode="&#xd;" />
<glyph unicode=" " />
<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
<glyph unicode="&#xa0;" />
<glyph unicode="&#x2000;" horiz-adv-x="652" />
<glyph unicode="&#x2001;" horiz-adv-x="1304" />
<glyph unicode="&#x2002;" horiz-adv-x="652" />
<glyph unicode="&#x2003;" horiz-adv-x="1304" />
<glyph unicode="&#x2004;" horiz-adv-x="434" />
<glyph unicode="&#x2005;" horiz-adv-x="326" />
<glyph unicode="&#x2006;" horiz-adv-x="217" />
<glyph unicode="&#x2007;" horiz-adv-x="217" />
<glyph unicode="&#x2008;" horiz-adv-x="163" />
<glyph unicode="&#x2009;" horiz-adv-x="260" />
<glyph unicode="&#x200a;" horiz-adv-x="72" />
<glyph unicode="&#x202f;" horiz-adv-x="260" />
<glyph unicode="&#x205f;" horiz-adv-x="326" />
<glyph unicode="&#x20ac;" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
<glyph unicode="&#x2212;" d="M200 400h900v300h-900v-300z" />
<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
<glyph unicode="&#x2601;" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
<glyph unicode="&#x2709;" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
<glyph unicode="&#x270f;" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
<glyph unicode="&#xe001;" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
<glyph unicode="&#xe002;" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
<glyph unicode="&#xe003;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
<glyph unicode="&#xe005;" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
<glyph unicode="&#xe006;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
<glyph unicode="&#xe007;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
<glyph unicode="&#xe008;" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
<glyph unicode="&#xe009;" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
<glyph unicode="&#xe010;" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe011;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe012;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe013;" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
<glyph unicode="&#xe014;" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
<glyph unicode="&#xe015;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
<glyph unicode="&#xe016;" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
<glyph unicode="&#xe017;" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
<glyph unicode="&#xe018;" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
<glyph unicode="&#xe019;" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
<glyph unicode="&#xe020;" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
<glyph unicode="&#xe021;" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
<glyph unicode="&#xe022;" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
<glyph unicode="&#xe023;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
<glyph unicode="&#xe024;" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
<glyph unicode="&#xe025;" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
<glyph unicode="&#xe026;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
<glyph unicode="&#xe027;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
<glyph unicode="&#xe028;" d="M0 25v475l200 700h800l199 -700l1 -475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
<glyph unicode="&#xe029;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
<glyph unicode="&#xe030;" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
<glyph unicode="&#xe031;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
<glyph unicode="&#xe032;" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
<glyph unicode="&#xe033;" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
<glyph unicode="&#xe034;" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
<glyph unicode="&#xe035;" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
<glyph unicode="&#xe036;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
<glyph unicode="&#xe037;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
<glyph unicode="&#xe038;" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
<glyph unicode="&#xe039;" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
<glyph unicode="&#xe040;" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
<glyph unicode="&#xe041;" d="M0 700l1 475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
<glyph unicode="&#xe042;" d="M1 700l1 475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
<glyph unicode="&#xe043;" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
<glyph unicode="&#xe044;" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
<glyph unicode="&#xe045;" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
<glyph unicode="&#xe046;" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
<glyph unicode="&#xe047;" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
<glyph unicode="&#xe048;" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v71l471 -1q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
<glyph unicode="&#xe049;" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
<glyph unicode="&#xe050;" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
<glyph unicode="&#xe051;" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
<glyph unicode="&#xe052;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
<glyph unicode="&#xe053;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
<glyph unicode="&#xe054;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe055;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe056;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe057;" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
<glyph unicode="&#xe058;" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
<glyph unicode="&#xe059;" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
<glyph unicode="&#xe060;" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
<glyph unicode="&#xe062;" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
<glyph unicode="&#xe063;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
<glyph unicode="&#xe064;" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 139t-64 210zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
<glyph unicode="&#xe065;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
<glyph unicode="&#xe066;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
<glyph unicode="&#xe067;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q61 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l567 567l-137 137l-430 -431l-146 147z" />
<glyph unicode="&#xe068;" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
<glyph unicode="&#xe069;" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe070;" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe071;" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
<glyph unicode="&#xe072;" d="M200 0l900 550l-900 550v-1100z" />
<glyph unicode="&#xe073;" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
<glyph unicode="&#xe074;" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
<glyph unicode="&#xe075;" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
<glyph unicode="&#xe076;" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
<glyph unicode="&#xe077;" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
<glyph unicode="&#xe078;" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
<glyph unicode="&#xe079;" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
<glyph unicode="&#xe080;" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
<glyph unicode="&#xe081;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
<glyph unicode="&#xe082;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h600v200h-600v-200z" />
<glyph unicode="&#xe083;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141 z" />
<glyph unicode="&#xe084;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
<glyph unicode="&#xe085;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM364 700h143q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5 q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5t-53.5 -74.5t-19 -114zM500 300h200v100h-200 v-100z" />
<glyph unicode="&#xe086;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
<glyph unicode="&#xe087;" d="M0 500v200h195q31 125 98.5 199.5t206.5 100.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200v-206 q149 48 201 206h-201v200h200q-25 74 -75.5 127t-124.5 77v-204h-200v203q-75 -23 -130 -77t-79 -126h209v-200h-210z" />
<glyph unicode="&#xe088;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
<glyph unicode="&#xe089;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
<glyph unicode="&#xe090;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
<glyph unicode="&#xe091;" d="M0 547l600 453v-300h600v-300h-600v-301z" />
<glyph unicode="&#xe092;" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
<glyph unicode="&#xe093;" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
<glyph unicode="&#xe094;" d="M104 600h296v600h300v-600h298l-449 -600z" />
<glyph unicode="&#xe095;" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
<glyph unicode="&#xe096;" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
<glyph unicode="&#xe097;" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
<glyph unicode="&#xe101;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5h-207q-21 0 -33 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
<glyph unicode="&#xe102;" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111q1 1 1 6.5t-1.5 15t-3.5 17.5l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6 h-111v-100zM100 0h400v400h-400v-400zM200 900q-3 0 14 48t36 96l18 47l213 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
<glyph unicode="&#xe103;" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
<glyph unicode="&#xe104;" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
<glyph unicode="&#xe105;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
<glyph unicode="&#xe106;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
<glyph unicode="&#xe107;" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 34 -48 36.5t-48 -29.5l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
<glyph unicode="&#xe108;" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
<glyph unicode="&#xe109;" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
<glyph unicode="&#xe110;" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
<glyph unicode="&#xe111;" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
<glyph unicode="&#xe112;" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
<glyph unicode="&#xe113;" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
<glyph unicode="&#xe114;" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
<glyph unicode="&#xe115;" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
<glyph unicode="&#xe116;" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
<glyph unicode="&#xe117;" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
<glyph unicode="&#xe118;" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
<glyph unicode="&#xe119;" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
<glyph unicode="&#xe120;" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
<glyph unicode="&#xe121;" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
<glyph unicode="&#xe122;" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
<glyph unicode="&#xe123;" d="M74 350q0 21 13.5 35.5t33.5 14.5h18l117 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-8 -3 -23 -8.5 t-65 -20t-103 -25t-132.5 -19.5t-158.5 -9q-125 0 -245.5 20.5t-178.5 40.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
<glyph unicode="&#xe124;" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
<glyph unicode="&#xe125;" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q124 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 213l100 212h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
<glyph unicode="&#xe126;" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q124 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
<glyph unicode="&#xe127;" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
<glyph unicode="&#xe128;" d="M-101 651q0 72 54 110t139 38l302 -1l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 17 -10.5t26.5 -26t16.5 -36.5v-526q0 -13 -86 -93.5t-94 -80.5h-341q-16 0 -29.5 20t-19.5 41l-130 339h-107q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l107 89v502l-343 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM1000 201v600h200v-600h-200z" />
<glyph unicode="&#xe129;" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6.5v7.5v6.5v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
<glyph unicode="&#xe130;" d="M2 585q-16 -31 6 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM77 565l236 339h503 l89 -100v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
<glyph unicode="&#xe131;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM298 701l2 -201h300l-2 -194l402 294l-402 298v-197h-300z" />
<glyph unicode="&#xe132;" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l402 -294l-2 194h300l2 201h-300v197z" />
<glyph unicode="&#xe133;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
<glyph unicode="&#xe134;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
<glyph unicode="&#xe135;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60 q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5 t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5 q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 39 2 44q31 -13 58 -14.5t39 3.5l11 4q7 36 -16.5 53.5t-64.5 28.5t-56 23q-19 -3 -37 0 q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -24 17 -66.5t17 -43.5 q-9 2 -31 5t-36 5t-32 8t-30 14zM692 1003h1h-1z" />
<glyph unicode="&#xe136;" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
<glyph unicode="&#xe138;" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
<glyph unicode="&#xe139;" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
<glyph unicode="&#xe140;" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
<glyph unicode="&#xe141;" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM514 609q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
<glyph unicode="&#xe142;" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -78.5 -16.5t-67.5 -51.5l-389 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23 q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60 l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
<glyph unicode="&#xe143;" d="M80 784q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100q-71 70 -104.5 105.5t-77 89.5t-61 99 t-17.5 91zM250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-105 48.5q-74 0 -132 -83l-118 -171l-114 174q-51 80 -123 80q-60 0 -109.5 -49.5t-49.5 -118.5z" />
<glyph unicode="&#xe144;" d="M57 353q0 -95 66 -159l141 -142q68 -66 159 -66q93 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-8 9 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q7 -7 19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -17q47 -49 77 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
<glyph unicode="&#xe145;" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
<glyph unicode="&#xe146;" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
<glyph unicode="&#xe148;" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5 zM700 237q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
<glyph unicode="&#xe149;" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q33 1 103 -16t103 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z" />
<glyph unicode="&#xe150;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
<glyph unicode="&#xe151;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
<glyph unicode="&#xe152;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
<glyph unicode="&#xe153;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
<glyph unicode="&#xe154;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
<glyph unicode="&#xe155;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
<glyph unicode="&#xe156;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
<glyph unicode="&#xe157;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
<glyph unicode="&#xe158;" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
<glyph unicode="&#xe159;" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
<glyph unicode="&#xe160;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
<glyph unicode="&#xe161;" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
<glyph unicode="&#xe162;" d="M217 519q8 -19 31 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8h9q14 0 26 15q11 13 274.5 321.5t264.5 308.5q14 19 5 36q-8 17 -31 17l-301 -1q1 4 78 219.5t79 227.5q2 15 -5 27l-9 9h-9q-15 0 -25 -16q-4 -6 -98 -111.5t-228.5 -257t-209.5 -237.5q-16 -19 -6 -41 z" />
<glyph unicode="&#xe163;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
<glyph unicode="&#xe164;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
<glyph unicode="&#xe165;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
<glyph unicode="&#xe166;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe167;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe168;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe169;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 400l697 1l3 699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe170;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l249 -237l-1 697zM900 150h100v50h-100v-50z" />
<glyph unicode="&#xe171;" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
<glyph unicode="&#xe172;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
<glyph unicode="&#xe173;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
<glyph unicode="&#xe174;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
<glyph unicode="&#xe175;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
<glyph unicode="&#xe176;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
<glyph unicode="&#xe177;" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
<glyph unicode="&#xe178;" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
<glyph unicode="&#xe179;" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -116q-25 -17 -43.5 -51.5t-18.5 -65.5v-359z" />
<glyph unicode="&#xe180;" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
<glyph unicode="&#xe181;" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
<glyph unicode="&#xe182;" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q17 18 13.5 41t-22.5 37l-192 136q-19 14 -45 12t-42 -19l-118 -118q-142 101 -268 227t-227 268l118 118q17 17 20 41.5t-11 44.5 l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
<glyph unicode="&#xe183;" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-20 0 -35 14.5t-15 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
<glyph unicode="&#xe184;" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
<glyph unicode="&#xe185;" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
<glyph unicode="&#xe186;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
<glyph unicode="&#xe187;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
<glyph unicode="&#xe188;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
<glyph unicode="&#xe189;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
<glyph unicode="&#xe190;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
<glyph unicode="&#xe191;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
<glyph unicode="&#xe192;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
<glyph unicode="&#xe193;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
<glyph unicode="&#xe194;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
<glyph unicode="&#xe195;" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
<glyph unicode="&#xe197;" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300h200 l-300 -300z" />
<glyph unicode="&#xe198;" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104.5t60.5 178.5q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
<glyph unicode="&#xe199;" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
<glyph unicode="&#xe200;" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -11.5t1 -11.5q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/icons/select.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -3,22 +3,30 @@
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
/* IMPORTANT: READ README.md in this folder! Do not edit .css file directly! */
@import url("https://fonts.googleapis.com/css2?family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap");
@font-face {
font-family: 'Glyphicons Halflings';
src: url(/fonts/glyphicons-halflings-regular.eot);
src: url(/fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'), url(/fonts/glyphicons-halflings-regular.woff2) format('woff2'), url(/fonts/glyphicons-halflings-regular.woff) format('woff'), url(/fonts/glyphicons-halflings-regular.ttf) format('truetype'), url(/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg');
}
body {
font-family: Urbanist, Roboto, Arial, sans-serif;
font-weight: 400;
letter-spacing: .03em;
color: #000;
color: #4d4e53;
margin: 0;
background-color: white;
padding: 8px;
margin-bottom: 1rem;
font-size: 1rem;
.validation-info {
display: none;
color: #db0020
}
.italicSmall {
font-style: normal;
font-size: 14px;
font-weight: normal;
font-style: italic;
font-size: 12px;
}
span {
@ -28,79 +36,77 @@ body {
a {
color: #0070D1;
text-decoration: none;
font-weight: 500;
&:hover {
text-decoration: underline;
color: #167cac;
}
}
.form-group {
display: flex;
flex-direction: column;
display: block;
position: relative;
}
.headerlinecontainer {
color: #000;
.headerline1 {
font-size: 20px;
line-height: 32px;
font-weight: 400;
margin: 24px 0;
text-align: center;
margin-bottom: .75rem;
margin-top: 0rem;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.375rem;
}
.headerline5v2 {
display: block;
padding: .75rem 0;
color: #db0020;
font-style: italic;
font-size: 12px;
}
}
input,
select {
margin-top: 4px;
}
input,
select {
height: 2.8125rem;
border: solid 1px #b3b4b5;
border-radius: 4px;
border: 1px solid #8E9292;
padding: 16px;
font-size: 16px;
box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2);
color: #525656;
font-family: inherit;
letter-spacing: inherit;
color: #000;
background-color: #fff;
font-weight: normal;
font-style: normal;
font-stretch: normal;
font-size: 16px;
line-height: 1.56;
padding: 10px;
width: 100%;
&:focus,
&:focus-visible {
box-shadow: 0 0 0 2.5px #1574a1;
border: 1px solid transparent;
border: solid 2.5px #0070d1;
outline: none;
}
&:disabled,
&.disabled {
background-color: #f5f5f5;
filter: grayscale(100%);
&:hover {
box-shadow: 0 0 0 4px transparent;
border: 1px solid #8e9292;
}
}
&:hover {
border: 1px solid #8e9292;
box-shadow: 0 0 0 4px #9fcee6;
}
}
select {
border-radius: 4px;
border: 1px solid #8E9292;
padding: 16px;
width: 100%;
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: 95% 50%;
background-size: 15px 9px;
background: url(/icons/select.png) no-repeat 98% #ffffff;;
background-size: 10px 17px;
appearance: none;
font-size: 16px;
color: #000;
}
h3 {
font-size: 1rem;
font-weight: 600;
margin-top: 1.375rem;
margin-bottom: .6875rem;
color: #000;
}
.creditCardSpecific {
div {
padding: 8px 0;
.headerlinecontainer {
display: none;
}
label {
font-weight: 500;
}
@ -133,63 +139,57 @@ body {
span {
display: none;
}
&:after {
content: " (optional)";
font-weight: 300;
font-style: normal;
font-size: 15px;
}
}
.buttonContainer {
height: 48px;
button {
position: relative;
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
#sghopcancelbutton {
float: left;
}
#sghopsubmitbutton {
float: right;
}
#sghopsubmitbutton.creditCardSpecific {
float: right;
@media(max-width: 500px) {
float: left;
width: 100%;
}
}
#selectPaymentTypeButton {
float: right;
@media(max-width: 500px) {
float: left;
width: 100%;
}
}
.btn-success {
width: 99%;
height: 45px;
border: none;
border-radius: 8px;
color: #fff;
justify-content: center;
font-weight: 500;
border-radius: 22.5px;
font-size: 16px;
font-family: inherit;
letter-spacing: inherit;
height: 48px;
@media (hover: hover) {
background: linear-gradient(270deg, #1574a1 0%, #003d58 100%);
cursor: pointer;
line-height: 0.94;
font-weight: 600;
color: #ffffff;
background-color: #0c7e47;
border-color: #0a673a;
font-family: Urbanist;
cursor: pointer;
&:hover {
background-color: #084f2d;
}
&:focus {
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
}
&:focus, // Mouse, touch, stylus focus
&:focus-visible {
// Keyboard focus for accessibility
&:focus,
&:active {
outline: none;
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
color: #fff;
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
}
&:disabled {
background: #e3e4e4 !important;
background: linear-gradient(270deg, #e3e4e4 0%, #e3e4e4 100%) !important;
color: #4d5151 !important;
font-weight: 400;
height: 48px;
border: none;
border-radius: 8px;
cursor: pointer;
pointer-events: all;
}
&.has-loader {
color: #fff;
background: #06577c;
box-shadow: 0 0 0 3px, 0 0 0 5.5px #06577c;
pointer-events: auto;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
background-color: #0c7e47;
box-shadow: 0 0 0 3px #ffffff, 0 0 0 5.5px #0c7e47;
}
}
}
@ -198,26 +198,42 @@ body {
.has-error {
input,
select {
border: 1px solid #d4281c;
&:focus {
box-shadow: 0 0 0 2.5px #fcbfbb;
}
&:hover {
border: 1px solid #d4281c;
box-shadow: 0 0 0 4px #fcbfbb;
&.form-control {
border-color: #db0020;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
&:focus {
box-shadow: none;
}
}
}
select {
border: 1px solid #d4281c;
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='%23d4281c'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: 95% 50%;
background-size: 15px 9px;
appearance: none;
background-image: url(/icons/select-error.png);
}
small {
margin-top: 4px;
color: #d4281c;
font-size: 1rem;
line-height: 1.56;
margin-bottom: .3125rem;
color: #db0020;
}
}
.has-success {
.control-label {
color: #4d4e53
}
input,
select {
&.form-control {
border-color: #0c7e47;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
&:focus {
box-shadow: none;
}
}
}
select {
background-image: url(/icons/select-success.png);
}
}
@ -234,7 +250,11 @@ body {
.list-group {
&.radio {
.list-group-item {
border-radius: 10px;
border-radius: 5px;
&.active {
box-shadow: 0 0 0 5px #b9d7e6;
}
label {
padding: 12px 20px;
@ -246,8 +266,7 @@ body {
#applepay-and-paypal-container {
display: inline-flex;
width: 100%;
width: 80%;
justify-content: space-between;
#paypalParentDiv {
@ -256,49 +275,38 @@ body {
.form-group {
margin: 0;
width: 100%;
width: 48%;
.list-group {
&.paymentSelection {
.list-group-item {
border-radius: 35px;
#applepayParentLink {
margin: 0;
padding: 10px 0;
background-color: #000000;
label {
padding: 0;
apple-pay-button {
vertical-align: middle;
}
}
&.list-group-item {
&#applepayParentLink {
margin: 0;
border: none;
box-shadow: none;
border-radius: 10px;
border: 1px solid #8E9292;
}
&:host {
--apple-pay-button-width: auto;
label {
padding: 0;
apple-pay-button {
width: 100%;
vertical-align: middle;
--apple-pay-button-height: 47px;
--apple-pay-button-border-radius: 35px;
--apple-pay-button-padding: 2px 0;
}
}
}
}
}
#paypalParentLink {
margin: 0;
padding: 2px 0;
#paypalImage {
max-width: 50%;
}
&.list-group-item {
box-shadow: none;
border-radius: 10px;
border: 1px solid #8E9292;
max-width: 90px;
}
}
}
@ -324,7 +332,7 @@ body {
#paypalParentLink {
#paypalImage {
max-width: 25%;
max-width: 5.3125rem;
}
}
}
@ -334,14 +342,19 @@ body {
#other-payments-container {
label {
display: inline-flex;
display: block;
text-align: left;
align-items: flex-start;
justify-content: space-between;
font-size: 16px;
font-weight: 500;
font-weight: 600;
line-height: 1.56;
&.control-label {
justify-content: start;
}
span {
&.paymentOptionText {
@ -367,14 +380,28 @@ body {
}
#creditCardParentDiv {
#creditCardParentLink {
margin-bottom: 0;
@media (max-width: 33.9em) {
min-height: 3.5rem;
}
label {
display: flex;
.paymentOptionText {
text-align: left;
}
}
}
&.active {
margin-bottom: 32px;
padding-bottom: 10px;
border: 1px solid #0070D1;
border-radius: 10px;
border-radius: 5px;
> .paymentSelection {
#creditCardParentLink {
border-width: 0 0 2px 0;
min-height: unset;
border-bottom-color: #0070D1;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
@ -384,20 +411,28 @@ body {
.paymentSelection {
#creditCardImages {
display: flex;
flex-direction: row;
width: 50%;
text-align: right;
justify-content: flex-end;
> img {
width: 20%;
img {
max-width: fit-content;
width: 25%;
&:not(:last-of-type) {
margin-right: 1%;
}
}
}
}
.creditCardSpecific {
padding: 0 16px;
padding: 0 2%;
> .form-group {
width: 100%;
margin-bottom: .5rem;
}
}
}
@ -407,7 +442,7 @@ body {
}
.disclaimerContainerv2 {
margin-bottom: 32px;
margin: 1rem 0;
}
}
@ -422,4 +457,27 @@ body {
}
}
}
}
.form-control-feedback {
position: absolute;
top: 0;
right: 0;
z-index: 2;
display: block;
width: 38px;
height: 38px;
line-height: 38px;
text-align: center;
pointer-events: none;
top: 27px;
&.glyphicon {
display: none;
}
&.glyphicon-remove:before {
content: "\e014";
}
&.glyphicon-ok:before {
content: "\e013";
}
}

View file

@ -5,107 +5,20 @@
/* Style the modal when PayPal button is selected */
#pageLoadingModal {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.40);
display: flex;
.modal-content {
margin: auto auto;
}
p {
margin: 0;
text-transform: uppercase;
font-size: 14px;
}
.modal {
display: none;
overflow: hidden;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1040;
-webkit-overflow-scrolling: touch;
outline: 0;
}
.modal-content {
position: relative;
background-color: #fff;
border-radius: 8px;
background-clip: padding-box;
outline: 0;
width: 176px;
height: 136px;
display: flex;
justify-content: center;
align-items: center;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: center center;
background-size: 96px;
&:after {
content: '';
border-radius: 50%;
position: absolute;
width: 5.5rem;
height: 5.5rem;
border: 0.45em solid #d4281c;
border-right-color: transparent;
animation: spinnerrotate 0.75s linear infinite;
}
@keyframes spinnerrotate {
0% { transform: rotate(0)}
100% { transform: rotate(360deg)}
}
p {
display: none;
}
}
.modal-backdrop {
position: absolute;
top: 0;
right: 0;
left: 0;
background-color: #000000;
&.fade {
opacity: 0;
filter: alpha(opacity=0);
}
}
&.hide {
display: none;
}
display: none;
}
// AfterPay Styles
.buy-backdrop {
background-color: rgba(0,0,0,.3)!important;
background-color: rgba(0,0,0,.8)!important;
#afterpay__iframe-checkout-container {
width: 100%;
.style_closeWrapper__lIHJe {
display: none;
}
}
}
.buy-container-closer {
display: none;
}
// Modal Spinner
@keyframes rotate {

View file

@ -26,7 +26,9 @@ const applicationConfig = Object.freeze({
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging",
BAILOUT_ON_APPLICATION_ERROR: true,
BAILOUT_ON_API_ERROR: true,
BAILOUT_ON_ROUTER_ERROR: true
BAILOUT_ON_ROUTER_ERROR: true,
ADYEN_CLIENT_KEY: process.env.VUE_APP_ADYEN_CLIENT_KEY,
ADYEN_ENVIRONMENT: process.env.VUE_APP_ADYEN_ENVIRONMENT
});
export default applicationConfig;

View file

@ -1,3 +1,4 @@
// When adding a new bailout, the Digital Consumer Order Service must also be updated to map the new bailout code to the SV2BailoutCode
const bailoutCode = Object.freeze({
Unknown: 0,
SaveSessionError: 1,
@ -15,7 +16,8 @@ const bailoutCode = Object.freeze({
VehicleYMMSLookupError: 13,
ApplicationError: 14,
ApiError: 15,
RouterError: 16
RouterError: 16,
CoverageCancelledByUser: 17
});
export default bailoutCode;

View file

@ -63,7 +63,7 @@ const bailoutMessage = Object.freeze({
}),
HeavyTruckVehicle: (carId) => ({
code: bailoutCode.HeavyTruckVehicle,
message: `User selected a heavy truck vehicle. Car ID: ${carId}`
message: `User selected a big truck that Safelite cannot service. Car ID: ${carId}`
}),
NoPartsAvailable: () => ({
code: bailoutCode.NoPartsAvailable,
@ -93,7 +93,11 @@ const bailoutMessage = Object.freeze({
code: bailoutCode.VehicleYMMSLookupError,
message: getMessage(year, make, model, style)
};
}
},
CoverageCancelledByUser: () => ({
code: bailoutCode.CoverageCancelledByUser,
message: 'User canceled claim on ITAC/No Comp coverage statement.'
})
});
export default bailoutMessage;

View file

@ -9,7 +9,3 @@ export const dropdownVariants = Object.freeze({
primary: 'primary',
compact: 'compact'
});
export const modalPositions = Object.freeze({
center: 'center',
edge: 'edge'
});

View file

@ -14,8 +14,7 @@ const cookieNames = Object.freeze({
// Existing Safelite.com cookies
DXDEV: 'dxdev',
SESSION_ID: 'sid',
SESSION_KEY: 'skey'
SESSION_ID: 'sid'
});
const cookieExpirations = Object.freeze({

View file

@ -10,6 +10,7 @@ const PARTS_BASE_URL = '/parts/api/v1/parts';
const PRICE_BASE_URL = '/price/api/v1/price';
const SCHEDULE_BASE_URL = '/schedule/api/v1/schedule';
const VEHICLE_BASE_URL = '/vehicle/api/v1/vehicle';
const PAYMENT_BASE_URL = '/payment/api/v1/payment';
const endpoints = Object.freeze({
GetRouteInfo: {
@ -239,6 +240,14 @@ const endpoints = Object.freeze({
GetPaymentSignature: {
url: `${ORDER_BASE_URL}/sign`,
method: 'POST'
},
InitializeAdyenPayment: {
url: `${PAYMENT_BASE_URL}/payment/session/initialize-session`,
method: "POST",
},
GetAdyenSessionResult: {
url: `${PAYMENT_BASE_URL}/payment/session/session-result`,
method: "POST",
}
});

View file

@ -10,7 +10,7 @@ const errorMessages = Object.freeze({
PASSENGER_SIDE_OPTIONS_REQUIRED: 'Please select window',
WINDSHIELD_DAMAGE_TYPE_REQUIRED: 'Please select windshield damage',
WINDSHIELD_CHIP_COUNT_REQUIRED: 'Please select chip(s)',
WINDSHIELD_REPLACE_OPTIONS_REQUIRED: 'Please select windshield part',
WINDSHIELD_REPLACE_OPTIONS_REQUIRED: 'Select all windshield parts that need to be replaced',
REPLACE_OPTIONS_REQUIRED: 'Please select rear window type',
STREET_ADDRESS_REQUIRED: 'Street Address is required.',
CITY_REQUIRED: 'City is required.',

View file

@ -1,7 +1,8 @@
const experimentUniverses = Object.freeze({
ISS_FUNNEL: 'ISSFunnel',
ISS_FEATURETOGGLE_AREFEES_HIDDEN: 'NextGenISS_FeatureToggle_AreFeesHidden',
ISS_FEATURETOGGLE_AREFEES_OVERRIDDEN: 'NextGenISS_FeatureToggle_AreFeesOverridden'
ISS_FEATURETOGGLE_AREFEES_OVERRIDDEN: 'NextGenISS_FeatureToggle_AreFeesOverridden',
ADYEN_PAYMENT_TEST: 'NextGenAdyenPaymentTest'
});
const experimentSettings = Object.freeze({
@ -10,7 +11,8 @@ const experimentSettings = Object.freeze({
ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_HIDDEN: 'HideMobileFee',
ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_OVERRIDDEN: 'OverrideMobileFee',
ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_HIDDEN: 'HideRecycleFee',
ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_OVERRIDDEN: 'OverrideRecycleFee'
ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_OVERRIDDEN: 'OverrideRecycleFee',
ISS_ENABLE_ADYEN_V1: 'ISS_Enable_Adyen_V1'
});
const experimentTriggers = Object.freeze({

View file

@ -1,5 +1,6 @@
const partNumberStrings = Object.freeze({
RECYCLE_FEE: 'RECYCLE FEE'
RECYCLE_FEE: 'RECYCLE FEE',
LABOR2: 'LABOR2',
});
export default partNumberStrings;

View file

@ -66,6 +66,14 @@ export const pageProgressMapper = {
// No progress bar on this page
percent: 0
},
'payment-page-adyen': {
// No progress bar on this page
percent: 0
},
'payment-return-adyen': {
// No progress bar on this page
percent: 0
},
'tpa-search': {
percent: 80
},

View file

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

View file

@ -11,22 +11,19 @@
'hidden.bs.modal': onModalClosed,
'shown.bs.modal': onModalOpened
}">
<div class="modal-dialog"
:class="{
'modal-dialog-edge': modalPosition === modalPositions.edge,
'modal-dialog-centered': modalPosition === modalPositions.center
}">
<div class="modal-dialog">
<div
class="modal-content"
role="dialog"
aria-modal="true">
<div class="modal-header mb-2 mt-2">
<div class="modal-header">
<span
v-if="headerText"
class="modal-title d-flex justify-content-center pb-0 w-100"
class="modal-title d-flex pb-0 w-100"
:innerHTML="headerText">
</span>
<button
v-if="displayCloseButton"
ref="closeButton"
type="button"
class="btn-close"
@ -34,22 +31,30 @@
@mousedown="closeModal"
@keypress.space="closeModal"></button>
</div>
<div class="modal-body ps-5 pe-5 pb-5 pt-1">
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer px-5 py-4">
<div class="modal-footer">
<buttonMain
v-if="displayFooterButton"
id="modalbtn"
ref="modalButtonMain"
:variant="buttonVariant"
class="w-100"
:buttonText="footerButtonText"
pushToGA
:variant="buttonVariant"
:class="[
(isButtonDisabled || isFooterButtonDisabled) &&
'form-test-invalid'
]"
@clickEvent="validateAndEmit" />
<textLink
v-if="displayCloseLink"
class="mt-4 mb-0"
linkType="text"
:text="closeLinkText"
href="#!"
@click="closeModal" />
</div>
</div>
</div>
@ -60,12 +65,13 @@
import ButtonMain from '@/ux-components/button-main/button-main.vue';
import { Modal } from 'bootstrap';
import { useForm } from 'vee-validate';
import { modalPositions } from '@/constants/component-variants';
import textLink from '@/ux-components/text-link/text-link.vue';
export default {
name: 'modal',
components: {
ButtonMain
ButtonMain,
textLink
},
props: {
modalId: String,
@ -82,13 +88,25 @@ export default {
type: String,
default: 'success'
},
modalPosition: {
type: String,
default: modalPositions.edge
},
allowInvalidSubmit: {
type: Boolean,
default: false
},
displayFooterButton: {
type: Boolean,
default: true
},
displayCloseButton: {
type: Boolean,
default: true
},
displayCloseLink: {
type: Boolean,
default: false
},
closeLinkText: {
type: String,
default: 'Close window'
}
},
emits: ['footer-button-event', 'isModalOpened'],
@ -105,8 +123,7 @@ export default {
modalId,
meta,
validate,
resetForm,
modalPositions
resetForm
};
},
computed: {
@ -159,8 +176,12 @@ export default {
@import "@/styles/ux-variables-svg-strings.scss";
.modal {
top: auto;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding-right: 1rem;
h5,
strong,
.subheader-text {
@ -178,10 +199,11 @@ export default {
}
.modal-title {
font-weight: 500;
font-weight: 400;
font-size: 1.25rem;
line-height: 2rem;
color: $black;
padding-left: 1.5rem;
}
}
.modal-footer {
@ -190,7 +212,7 @@ export default {
bottom: 0;
z-index: 5;
border-radius: 0;
background-color: $gray-100;
justify-content: center;
> .navigation-link {
color: $black;
@ -205,10 +227,19 @@ export default {
&.modal-component {
.modal-dialog {
display: flex;
max-width: 47.9375rem;
width: 37.5rem;
max-width: unset;
min-height: auto;
.modal-content {
box-shadow: 0px 1rem 3rem -1rem rgba(0, 0, 0, 0.25);
border: 1px solid;
border-radius: 5px;
overflow: auto;
}
.modal-content {
margin: 1.5rem auto 0 auto;
margin: 0 auto;
.modal-body {
padding: 0 1.5rem 0 1.5rem;
.col {
max-width: 100%;
}
@ -217,6 +248,11 @@ export default {
}
ul {
margin-bottom: 0;
padding-left: 20px;
}
li {
margin-bottom: 0.5rem;
line-height: 22px;
}
p {
&:last-child {
@ -233,51 +269,26 @@ export default {
}
}
}
&.modal-dialog-edge {
min-height: 100%;
margin: 0 auto;
align-items: flex-end;
@include media-breakpoint-up(md) {
align-items: inherit;
width: 23.5rem;
position: absolute;
top: 0;
right: 0;
transform: translate(100%);
}
.modal-content {
box-shadow: 0px 1rem 3rem -1rem rgba(0, 0, 0, 0.25);
border-radius: 1.5rem 1.5rem 0 0;
@include media-breakpoint-up(md) {
box-shadow: none;
border-radius: 0;
margin: 0 auto;
}
}
}
&.modal-dialog-centered {
.modal-content {
box-shadow: 0px 1rem 3rem -1rem rgba(0, 0, 0, 0.25);
border-radius: 1.5rem;
overflow: auto;
}
.modal-header {
justify-content: center;
}
}
}
&.show .modal-dialog {
&.modal-dialog-edge {
@include media-breakpoint-up(md) {
transform: translateY(0);
}
}
}
.modal-footer {
border-top: none;
padding: .625rem 1.5rem 1.5rem 1.5rem;
button {
margin: 0;
background-color: $white;
color: $blue;
border-color: $blue;
font-weight: 400;
&:hover {
background-color: $heritage-blue-secondary;
color: $white;
border-color: $heritage-blue-secondary;
}
&:active,
&:focus {
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $heritage-blue-secondary;
}
}
}
}
@ -286,6 +297,15 @@ export default {
transition: opacity 0.25s linear;
}
}
@include media-breakpoint-down(md) {
&.modal-component {
.modal-dialog {
width: auto;
max-width: unset;
margin: .625rem;
}
}
}
}
body {
.modal-backdrop {

164
src/helpers/adyen-helper.js Normal file
View file

@ -0,0 +1,164 @@
import { AdyenCheckout } from "@adyen/adyen-web/auto";
import { useMainStore } from "@/store";
import globalMethods from "@/global-methods";
import endpoints from "@/constants/endpoints";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import { paymentMethods } from "@/constants/payment-method-constants";
import applicationConfig from "@/constants/application-config";
export async function getSessionInfo(sessionId, encryptedSessionResult) {
const order = useMainStore().order;
const location = getLocationInfo(order);
const workOrder = getWorkOrderLastSixDigits(order);
const system = getSourceSystem();
const request = {
sessionId: sessionId,
sessionResult: encryptedSessionResult,
zipCodeCtu: location.zipCodeCtu,
workOrderNumber: workOrder,
sourceSystem: system,
};
const response = await globalMethods.callHttpClient({
method: endpoints.GetAdyenSessionResult.method,
endpoint: endpoints.GetAdyenSessionResult.url,
payload: request,
logApiCall: true
});
return response?.data;
}
export async function createAdyenCheckout({
session: { id, sessionData },
handlers: { onPaymentCompleted, onPaymentFailed, onError },
options: { showPayButton = true, amount },
}) {
const config = {
session: {
id: id,
sessionData: sessionData,
},
clientKey: applicationConfig.ADYEN_CLIENT_KEY,
environment: applicationConfig.ADYEN_ENVIRONMENT,
locale: "en_US",
countryCode: "US",
showPayButton: showPayButton,
translations: {
en_US: {
"creditCard.securityCode.label": "CVV/CVC",
},
},
onPaymentCompleted: onPaymentCompleted,
onPaymentFailed: onPaymentFailed,
onError: onError,
};
if (amount !== undefined && amount !== null) {
config.amount = {
value: amount,
currency: "USD",
};
}
return await AdyenCheckout(config);
}
export function mapAdyenToIssPaymentMethod(adyenMethod) {
const paymentMethodMap = {
["scheme"]: paymentMethods.CREDIT_CARD,
["afterpaytouch_US"]: paymentMethods.AFTERPAY,
["paypal"]: paymentMethods.PAYPAL,
["applepay"]: paymentMethods.APPLEPAY,
};
const match = paymentMethodMap[adyenMethod];
return match ?? paymentMethods.CREDIT_CARD;
}
export function mapIssToAdyenPaymentMethod(issMethod) {
const paymentMethodMap = {
[paymentMethods.CREDIT_CARD]: "scheme",
[paymentMethods.AFTERPAY]: "afterpaytouch_US",
[paymentMethods.PAYPAL]: "paypal",
[paymentMethods.APPLEPAY]: "applepay",
[paymentMethods.PayNow]: null,
[paymentMethods.PAY_AT_TIME_OF_SERVICE]: null,
};
const match = paymentMethodMap[issMethod];
return match;
}
export function generateCcToken(adyenSessionInfo) {
const zipCode = useMainStore().order.customer.address.zipCode
?? useMainStore().order.serviceLocation.zipCode
?? useMainStore().order.serviceLocation.provider.address.zipCode
const ccToken = {
subscriptionId: adyenSessionInfo?.storedToken,
expMonth: adyenSessionInfo?.cardExpiryMonth,
expYear: adyenSessionInfo?.cardExpiryYear,
cardType: adyenSessionInfo?.cardType,
billToPostalCode: zipCode,
billToFirstName: useMainStore().contactInfo.firstName,
billToLastName: useMainStore().contactInfo.lastName,
referenceNumber: adyenSessionInfo?.workOrderNumber,
authCode: adyenSessionInfo?.authCode,
transactionId: adyenSessionInfo?.transactionReference,
transReferenceNumber: adyenSessionInfo?.transactionReference,
lastFour: adyenSessionInfo?.last4DigitsOfCard,
};
return ccToken;
}
function getLocationInfo(order) {
const serviceLocation = order.serviceLocation;
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE ||
serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
if (isMobile) {
return {
address: serviceLocation.address,
address2: serviceLocation.address2,
city: serviceLocation.city,
state: serviceLocation.state,
zipCode: serviceLocation.zipCode,
zipCodeCtu: serviceLocation.zipCodeCtu,
};
} else {
const providerInfo = serviceLocation.provider.address;
return {
address: providerInfo.streetAddress,
address2: "",
city: providerInfo.city,
state: providerInfo.state,
zipCode: providerInfo.zipCode,
zipCodeCtu: providerInfo.zipCodeCtu,
};
}
}
function getWorkOrderLastSixDigits(order) {
const workOrderNumber = order.workOrderNumber ?? "";
const tokens = workOrderNumber.split("-");
const lastSegment = tokens[tokens.length - 1] ?? "";
const numDigits = lastSegment.length;
const numZeroes = 6 - numDigits;
let result = "";
for (let i = 0; i < numZeroes; i++) {
result += "0";
}
result += lastSegment;
return result;
}
function getSourceSystem() {
return "ISS-NextGen";
}

View file

@ -12,6 +12,8 @@ function canBailoutNavigateBack() {
case BailoutCode.VehicleNotFound:
case BailoutCode.RequestCallback:
case BailoutCode.TPANotEnabled:
case BailoutCode.CoverageCancelledByUser:
case BailoutCode.HeavyTruckVehicle:
return true;
default:

View file

@ -1,3 +1,21 @@
export function supportsApplePay() {
return !!(window.ApplePaySession && window.ApplePaySession.canMakePayments());
var iOSversion = getiOSversion();
var isMacOS = navigator.platform.indexOf("Mac") !== -1;
if (isMacOS || (iOSversion && iOSversion[0] >= 17)) {
if (window.ApplePaySession && window.ApplePaySession.canMakePayments()) {
return true;
}
}
return false;
}
function getiOSversion() {
if (/iP(hone|od|ad)/.test(navigator.platform)) {
// supports iOS 2.0 and later:
var v = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
if (!v) {
return null;
}
return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)];
}
return null;
}

View file

@ -165,7 +165,7 @@ export function getDeviceIdValue() {
Gets value of skey cookie, returns 0 if not found.
*/
export function getSessionKeyValue() {
const cookieValue = getCookieValueByName(cookieNames.SESSION_KEY);
const cookieValue = getCookieValueByName(cookieNames.ISS_SESSION_KEY);
if (cookieValue) {
return cookieValue;
@ -281,7 +281,7 @@ export function setSessionIdIfUnset(value) {
}
export function setSessionKeyIfUnset(value) {
if (!isCookieSet(cookieNames.ISS_SESSION_KEY)) {
if (getSessionKeyValue() === 0) {
setCookieProperties(
{
[cookieNames.ISS_SESSION_KEY]: value

View file

@ -20,6 +20,7 @@ Object {
"salesTax": "SalesTaxWidget",
"servicePackage": "ServicePackageTitle",
"subtotal": "SubtotalWidget",
"total": "TotalWidget",
"vapsItemDescriptions": "VapsItemDescriptions",
"warrantyText": "WarrantyCartItemTextWidget",
},

View file

@ -12,6 +12,7 @@ import { getHighestFullySatisfiedTier, getPackageContents } from '@/helpers/serv
import { getPriceOfLineItems } from '@/helpers/price-calculator.js';
import coverageStatuses from '@/constants/coverage-statuses';
import coverageType from '@/constants/coverage-type';
import * as cartHelper from '@/helpers/cart-helper';
const VERIFYING_COVERAGE = 'Verifying coverage';
@ -341,21 +342,76 @@ describe('cart-dropdown component', () => {
});
describe('computed', () => {
describe('amountDue', () => {
test('returns 0 when showAsPaid is true', () => {
test('returns total when showAsPaid is false', () => {
// Arrange
const expectedAmount = 123.45;
const propsData = {
showAsPaid: true
showAsPaid: false
};
const { wrapper } = getMountedComponent({}, {}, propsData);
const totalSpy = jest.spyOn(cartHelper, 'getCartTotal').mockReturnValue(expectedAmount);
// Act
const result = wrapper.vm.amountDue;
// Assert
expect(result).toBe(expectedAmount);
});
test('returns total minus amount paid when showAsPaid is true', () => {
// Arrange
const total = 123.45;
const amountPaid = 23.45;
const expectedAmount = total - amountPaid;
const propsData = {
showAsPaid: true
};
const storeData = {
order: {
settledTenderAmount: amountPaid
}
};
const { wrapper } = getMountedComponent(storeData, {}, propsData);
const totalSpy = jest.spyOn(cartHelper, 'getCartTotal').mockReturnValue(total);
// Act
const result = wrapper.vm.amountDue;
// Assert
expect(result).toBe(expectedAmount);
});
});
describe('amountPaid', () => {
test('returns 0 when showAsPaid is false', () => {
// Arrange
const propsData = {
showAsPaid: false
};
const { wrapper } = getMountedComponent({}, {}, propsData);
// Act
const result = wrapper.vm.amountPaid;
// Assert
expect(result).toBe(0);
});
test('returns sum of subTotal and salesTax when showAsPaid is false', () => {
// TODO when subTotal and salesTax are finished
test('returns settled tender amount when showAsPaid is true', () => {
// Arrange
const expectedAmount = 123.45;
const propsData = {
showAsPaid: true
};
const storeData = {
order: {
settledTenderAmount: expectedAmount
}
};
const { wrapper } = getMountedComponent(storeData, {}, propsData);
// Act
const result = wrapper.vm.amountPaid;
// Assert
expect(result).toBe(expectedAmount);
});
});
describe('availableLineItems', () => {

View file

@ -1,6 +1,6 @@
<template>
<div
v-if="!isDeductibleOnly"
v-if="!isDeductibleOnly || showAsPaid"
class="cart-table">
<div class="cart-item-list">
<div
@ -53,7 +53,7 @@
id="cart-footer"
class="cart-footer">
<div
v-if="!isUnverified"
v-if="!isUnverified && !isDeductibleOnly"
id="cart-subtotal-and-tax"
class="subtotal-and-tax">
<div
@ -68,12 +68,23 @@
<span id="sales-tax-label">{{ salesTaxLabel }}</span>
<span id="sales-tax-value">{{ formatAmountInDollars(salesTax) }}</span>
</div>
<div
v-if="showAsPaid"
id="cart-amount-paid"
class="py-2 col d-flex justify-content-between">
</div>
<div
v-if="showAsPaid"
id="cart-total"
class="cart-item">
<div class="price-row">
<span id="total-label">{{ totalLabel }}</span>
<span id="total-value">{{ getDisplayed(total) }}</span>
</div>
</div>
<div
v-if="showAsPaid"
id="cart-amount-paid"
class="cart-item">
<div class="price-row">
<span id="amount-paid-label">{{ amountPaidLabel }}</span>
<span if="amount-paid-value">{{ getDisplayed(amountPaid) }}</span>
<span id="amount-paid-value">{{ getDisplayed(amountPaid) }}</span>
</div>
</div>
<div
@ -111,7 +122,7 @@ import {
getServiceLineItems, getSubtotal, isOrderITAC, isOrderNoComp,
isOrderUnverified
} from '@/helpers/cart-helper';
import { getPriceOfLineItems, getTaxOfLineItems } from '@/helpers/price-calculator';
import { getPriceOfLineItem, getPriceOfLineItems, getTaxOfLineItems } from '@/helpers/price-calculator';
import { processIfStatements } from '@/helpers/cms-content-helper';
const VERIFYING_COVERAGE = 'Verifying coverage';
@ -141,7 +152,8 @@ export default {
servicePackage: 'ServicePackageTitle',
vapsItemDescriptions: 'VapsItemDescriptions',
warrantyText: 'WarrantyCartItemTextWidget',
guaranteeText: 'GuaranteeCartItemTextWidget'
guaranteeText: 'GuaranteeCartItemTextWidget',
total: 'TotalWidget'
},
cartItemType
};
@ -176,7 +188,8 @@ export default {
},
servicePrice() {
const price = getPriceOfLineItems(getServiceLineItems(this.cartOrder)) ?? 0;
return price - this.recalibrationPrice;
const recycleFee = this.recycleFeeLineItem ? getPriceOfLineItem(this.recycleFeeLineItem) : 0;
return price - this.recalibrationPrice - recycleFee;
},
isUnverified() {
return isOrderUnverified(this.cartOrder);
@ -198,10 +211,10 @@ export default {
return getCartTotal(this.cartOrder);
},
amountDue() {
return this.showAsPaid ? 0 : this.total;
return this.total - this.amountPaid;
},
amountPaid() {
return !this.showAsPaid ? 0 : this.total;
return !this.showAsPaid ? 0 : this.cartOrder.settledTenderAmount;
},
availableLineItems() {
const { supportingItems, glassParts, otherParts, feeItems } = this.lineItems;
@ -278,6 +291,9 @@ export default {
salesTaxLabel() {
return this.getCmsContent(this.widget.salesTax, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
totalLabel() {
return this.getCmsContent(this.widget.total, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
recycleFeeCartItem() {
return this.recycleFeeLineItem
? this.getCartItem(
@ -350,6 +366,7 @@ export default {
removeVap(partType) {
const newVaps = useMainStore().lineItems.vaps?.filter((vap) => vap?.partType !== partType) ?? [];
useMainStore().updateVaps(newVaps);
useMainStore().updateServicePackage(null);
},
getCmsContentForVapsType(vapsPartType) {
const vapsItemDescriptions = this.getCmsContent(
@ -414,6 +431,10 @@ export default {
a:hover {
color: $heritage-blue-secondary;
}
#amount-paid-label {
font-weight: 600;
}
}
.cart-footer {

View file

@ -8,10 +8,6 @@ Object {
"firstName": "Frederick",
"isModalOpened": false,
"lastName": "Taylor",
"modalPositions": Object {
"center": "center",
"edge": "edge",
},
"phoneNumber": "606-009-2943",
"rules": Object {
"emailAddress": "email-required|email-address-format",

View file

@ -1,15 +1,16 @@
<template>
<modal
:ref="modalName"
:modalPosition="modalPositions.center"
:headerText="headerText"
:footerButtonText="footerButtonText"
:onModalClosedCallback="resetFormValues"
:displayCloseLink="true"
:closeLinkText="closeLinkText"
class="contact-details-drawer"
@isModalOpened="setModalStatus"
@footerButtonEvent="clickFooterButtonEvent">
<template v-if="isModalOpened">
<span class="name-section-label">{{ nameSectionLabel }}</span>
<div class="name-section-label">{{ nameSectionLabel }}</div>
<div class="row form-group">
<textboxQuestion
class="col-lg-6"
@ -71,7 +72,6 @@ import { useMainStore } from '@/store';
import globalRules from '@/constants/global-rules.js';
import MaskaFormattedMasks from '@/constants/maska-masks';
import widgetFields from '@/constants/cms-widget-fields.js';
import { modalPositions } from '@/constants/component-variants';
export default {
name: 'contact-details-drawer',
@ -109,7 +109,6 @@ export default {
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`,
extension: `${globalRules.EXTENSION_FORMAT}`
},
modalPositions,
detailsSaved: false
};
},
@ -132,6 +131,9 @@ export default {
nameSectionLabel() {
return this.getCmsContent(this.widget.firstNameQuestion, widgetFields.INPUT_QUESTION_WIDGET.QUESTION_TEXT);
},
closeLinkText() {
return this.getCmsContent(this.widget.drawerFooter, widgetFields.FOOTER_WIDGET.BACK_BUTTON_TEXT);
}
},
methods: {
setModalStatus(isOpened) {
@ -203,5 +205,58 @@ export default {
margin-top: .625rem;
}
}
:deep(.modal-dialog) {
.modal-content {
padding: 1.25rem 1.25rem 0;
.modal-header {
padding: 0;
.modal-title {
padding: 0;
font-size: 1rem;
}
}
.modal-body {
padding: 0;
overflow: hidden;
}
.modal-footer {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
align-items: flex-start;
padding: 0rem;
button {
background-color: $heritage-blue-primary;
color: $white;
font-weight: 600;
width: 10.75rem;
margin-bottom: 1.25rem;
&:hover {
background-color: #00559e;
}
}
a {
margin: .5rem 0 0;
font-weight: $font-weight-normal;
}
@include media-breakpoint-down(lg) {
align-items: center;
flex-direction: column;
button {
width: 100%;
}
}
}
}
}
}
</style>

View file

@ -6,14 +6,15 @@
@footerButtonEvent="footerButtonClick">
<img
:src="ModalImage"
class="mw-100 d-flex mx-auto mb-4"
class="mw-100 d-flex"
:class="imgClass"
alt="" />
<h5
class="mb-4"
class="header-text"
:class="cssModalHeadlineClass"
v-html="ModalHeadline"></h5>
<p
class="fw-bold mb-2 subheader-text"
class="fw-bold subheader-text"
v-html="ModalSubheadertext"></p>
<p
class="mb-0"
@ -59,7 +60,13 @@ export default {
},
ModalCloseButtonText() {
return this.getCmsContent(this.cmsWidgetName, 'FooterText');
}
},
imgClass() {
if (this.cmsWidgetName === 'RainDefenseModal') {
return 'rain-repel';
}
return '';
},
},
methods: {
openModal() {
@ -79,6 +86,10 @@ export default {
.modal-dialog {
.modal-content {
.modal-body {
.header-text {
margin-top: 2rem;
margin-bottom: 0.5rem;
}
.modal-sub-body {
color: $gray-600;
}
@ -100,4 +111,7 @@ export default {
}
}
}
.rain-repel {
height: 3.5rem;
}
</style>

View file

@ -8,18 +8,23 @@
:headerText="modalHeaderText"
:footerButtonText="modalFooterText"
buttonVariant="primary"
:displayCloseButton="false"
@isModalOpened="setModalStatus"
@footerButtonEvent="startNewReferral">
<p class="mb-2 subheader-text text-center">
<p class="continue-subheader">
{{ modalSubHeaderText }}
</p>
<buttonMain
id="btnContinueReferral"
variant="success"
class="mt-2 w-100"
class="continue-btn w-100"
:buttonText="modalBodyText"
@click-event="continueReferral">
</buttonMain>
<div class="or-container">
<hr/>
<span>or</span>
</div>
</modal>
</div>
</transition>
@ -58,7 +63,7 @@ export default {
},
modalSubHeaderText() {
return this.getCmsContent('ContinueReferralModalWidget', 'SubheaderText')
.replaceAll('{custom:mmFound}', this.getMakeModelString);
.replaceAll('{custom:mmFound}', this.getMakeModelString.toUpperCase());
}
},
methods: {
@ -82,5 +87,64 @@ export default {
</script>
<style lang="scss" scoped>
.continue-modal-container {
.continue-subheader {
margin-bottom: 1.25rem;
}
.continue-btn {
background-color: #098649;
}
.or-container {
position: relative;
hr {
margin: 1.875rem 0 1.25rem;
}
span {
position: absolute;
top: -0.75rem;
left: 50%;
margin-left: -1.625rem;
padding: 0 1.125rem;
background-color: white;
}
}
:deep(.modal) {
.modal-dialog {
.modal-content {
.modal-header {
padding: 1.25rem 1rem .625rem;
.modal-title {
padding: 0;
}
}
.modal-body {
padding: 0rem 1rem;
}
.modal-footer {
padding: 0rem 1rem 1rem;
button {
background-color: $heritage-blue-primary;
color: $white;
font-weight: 600;
margin-bottom: 1.25rem;
&:hover {
background-color: #00559e;
}
}
}
}
@include media-breakpoint-down(md) {
margin: 1.625rem 1rem 0;
}
}
}
}
</style>

View file

@ -29,7 +29,10 @@ export default {
}
},
async beforeMount() {
await this.createMapWithMarkersForAddresses(this.markers);
await this.createMapWithMarkersForAddresses(this.markers)
.catch(() => {
console.error('Error creating map with markers: if handled jest fails to run tests');
});
},
methods: {
async getMap(center) {

View file

@ -124,13 +124,7 @@ export default {
justify-content: flex-start;
align-items: center;
flex-direction: column;
@include media-breakpoint-up(xs) {
padding-top: 5rem;
}
@include media-breakpoint-up(md) {
padding-top: 7rem;
}
padding: 2rem;
.modal-loader-content-container {
min-width: 15rem; // 240px

View file

@ -0,0 +1,101 @@
<template>
<div class="recal-modal-container">
<modal
ref="recalModal"
:footerButtonText="modalCloseButtonText"
@footerButtonEvent="footerButtonClick">
<h5
class="header-text"
v-html="modalHeaderText"></h5>
<img
:src="modalImage"
class="mw-100 d-flex"
alt="" />
<div v-html="modalBodyText" class="recal-modal-body"></div>
</modal>
</div>
</template>
<script>
import modal from '@/digital-components/modal/modal.vue';
export default {
name: 'recal-modal',
components: {
modal
},
props: {
cmsWidgetName: {
type: String,
default: 'RecalModal'
}
},
computed: {
modalBodyText() {
return this.getCmsContent(this.cmsWidgetName, 'BodyText').replaceAll('{custom:vehicleMake}', this.vehicleMake);
},
modalHeaderText() {
return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
},
modalImage() {
return this.getCmsContent(this.cmsWidgetName, 'Image');
},
modalCloseButtonText() {
return this.getCmsContent(this.cmsWidgetName, 'FooterText');
},
vehicleMake() {
return this.mainStore.order.vehicle.make.toUpperCase();
}
},
methods: {
openModal() {
this.$refs.recalModal.openModal();
},
footerButtonClick() {
this.$refs.recalModal.closeModal();
},
}
};
</script>
<style lang="scss" scoped>
:deep(.modal) {
&.modal-component {
.modal-dialog {
.modal-content {
.modal-body {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
}
}
.modal-footer {
button {
&:hover {
background-color: $white;
color: $heritage-blue-secondary;
}
&:focus, &:active {
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $heritage-blue-secondary;
}
font-weight: 600;
color: $heritage-blue-secondary;
border-color: $heritage-blue-secondary;
}
}
}
.recal-modal-body {
padding: 10px 0 10px 0;
p:first-child {
margin-bottom: 10px;
}
p {
strong {
font-weight: 600;
color: $black;
}
}
}
}
</style>

View file

@ -21,9 +21,9 @@
'form-test-invalid': (disableForwardAction || isForwardActionDisabled),
'hide-button': isForwardButtonHidden
}"
:aria-disabled="disableForwardAction || isForwardActionDisabled"
:isDisabled="disableForwardAction || isForwardActionDisabled"
:isDisabled="disableForwardAction || isForwardActionDisabled || isForwardButtonHidden"
data-test-id="site-footer-main-button"
buttonType="submit"
@clickEvent="buttonClick" />
</div>
<div

View file

@ -466,76 +466,5 @@ describe('address-lookup.vue', () => {
// Assert
expect(wrapper.vm.navigateForwardWithSingleCarMatch).toHaveBeenCalledTimes(1);
});
test('selected vehicle is a non-serviceable big truck, display no service alert', async () => {
// Arrange
const mockRegistrationAddress = {
streetAddress: '1234 Main St',
city: 'Columbus',
state: 'OH',
zipCode: '43215'
};
const { wrapper } = setupMocks({
vinVehicles: [
{
vehicle: {
carId: 'BIGTRUCKCARID',
isBigTruck: true,
canSafeliteService: false
}
}
]
});
useMainStore().order.vehicle.carId = 'BIGTRUCKCARID';
await wrapper.setData({
customerQuestions: {
addressQuestions: mockRegistrationAddress
}
});
// Act
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.findComponent({ ref: 'alertNoService' }).isVisible()).toBe(true);
});
test('selected vehicle is a serviceable big truck, do not display no service alert', async () => {
// Arrange
const mockRegistrationAddress = {
streetAddress: '1234 Main St',
city: 'Columbus',
state: 'OH',
zipCode: '43215'
};
const { wrapper } = setupMocks({
vinVehicles: [
{
vehicle: {
carId: 'BIGTRUCKCARID2',
isBigTruck: true,
canSafeliteService: true
}
}
]
});
useMainStore().order.vehicle.carId = 'BIGTRUCKCARID2';
await wrapper.setData({
customerQuestions: {
addressQuestions: mockRegistrationAddress
}
});
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.findComponent({ ref: 'alertNoService' }).exists()).toBe(false);
});
});
});

View file

@ -55,14 +55,6 @@
alertClass="alert-danger"
:isCollapsible="true"
:isDismissible="false" />
<alert
v-if="displayNoServiceAlert"
ref="alertNoService"
class="mt-5"
cmsWidgetName="AlertNoServiceWidget"
alertClass="alert-danger"
:isCollapsible="true"
:isDismissable="false" />
<siteFooter
ref="siteFooter"
class="mt-5"
@ -142,8 +134,7 @@ export default {
isCarIdDifferent: false,
isSelectedGlassAvailableForVehicle: true,
customAlertData: {},
forwardButtonCarStyle: '',
displayNoServiceAlert: false
forwardButtonCarStyle: ''
};
},
computed: {
@ -224,6 +215,7 @@ export default {
async forwardButtonAction() {
this.resetWarningsAndErrors();
useMainStore().resetBailout();
// Vehicle info change in the flow, use a variable to keep track and commit to state at the end.
let vehicleInfoToCommit = {};
@ -261,9 +253,8 @@ export default {
const carFound = carsFound[0].vehicle;
if (!carFound.canSafeliteService) {
this.displayNoServiceAlert = true;
showIssLoadingModal(false);
return this.$refs.siteFooter.disableForwardButton();
this.mainStore.setBailout(bailoutMessage.HeavyTruckVehicle(carFound.carId));
return this.$router.push({ name: 'bailout' });
}
this.isCarIdDifferent = carFound.carId !== useMainStore().order.vehicle.carId;
@ -371,7 +362,6 @@ export default {
this.displayVinNotFoundAlert = false;
this.displayMatchedDifferentVehicleAlert = false;
this.displayVinLookupByHomeAddressNotAllowedAlert = false;
this.displayNoServiceAlert = false;
this.$refs.siteFooter.enableForwardAction();
}
}

View file

@ -144,7 +144,8 @@ describe('address-vehicles.vue', () => {
const { wrapper } = setupMocks({});
const lookupVinResponse = {
data: {
carId: '456'
carId: '456',
canSafeliteService: true
}
};
@ -247,94 +248,4 @@ describe('address-vehicles.vue', () => {
// Assert
expect(arePagePrerequisitesValid).toBe(true);
});
test('selected vehicle is a non-serviceable big truck, display no service alert', async () => {
// Arrange
const { wrapper } = setupMocks({});
useMainStore().order.vehicle.carId = 'BIGTRUCKCARID';
useMainStore().applicationUser = {
pageData: {
'address-vehicles':
[
{
vehicle: {
carId: 'BIGTRUCKCARID',
make: 'Isuzu',
model: 'Reach',
style: 'Step Van',
year: 2016,
isBigTruck: true,
canSafeliteService: false
},
vin: 'BIGTRUCKVIN'
}
]
}
};
await wrapper.setData({
selectedVehicleVin: 'BIGTRUCKVIN',
previouslyEnteredCarId: 'BIGTRUCKCARID'
});
wrapper.vm.selectedVehicle = {
vin: 'BIGTRUCKVIN',
vehicle: {
carId: 'BIGTRUCKCARID',
isBigTruck: true,
canSafeliteService: false
}
};
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.findComponent({ ref: 'AlertNoService' }).isVisible()).toBe(true);
});
test('selected vehicle is a serviceable big truck, do not display no service alert', async () => {
// Arrange
const { wrapper } = setupMocks({});
useMainStore().order.vehicle.carId = 'BIGTRUCKCARID2';
useMainStore().applicationUser = {
pageData: {
'address-vehicles':
[
{
vehicle: {
carId: 'BIGTRUCKCARID2',
make: 'Mack',
model: 'Terrapro',
style: 'Cabover',
year: 2024,
isBigTruck: true,
canSafeliteService: true
},
vin: 'BIGTRUCKVIN2'
}
]
}
};
await wrapper.setData({
selectedVehicleVin: 'BIGTRUCKVIN2',
previouslyEnteredCarId: 'BIGTRUCKCARID2'
});
wrapper.vm.selectedVehicle = {
vin: 'BIGTRUCKVIN2',
vehicle: {
carId: 'BIGTRUCKCARID2',
isBigTruck: true,
canSafeliteService: true
}
};
// Act
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.findComponent({ ref: 'AlertNoService' }).exists()).toBe(false);
});
});

View file

@ -13,13 +13,6 @@
<siteSubHeader
cmsWidgetName="SiteSubHeaderWidget"
class="mt-4" />
<alert
v-if="displayNoServiceAlert"
ref="AlertNoService"
class="mt-5"
cmsWidgetName="AlertVehicleSelection"
alertClass="alert-danger"
:isDismissable="false" />
<addressVehiclesQuestion
ref="addressVehiclesQuestion"
v-model="selectedVehicleVin"
@ -91,6 +84,7 @@ import {
} from '@/helpers/cms-content-helper.js';
import routerParams from '@/router/router-constants/router-params';
import vinPagesMixin from '@/mixins/vin-pages-mixin';
import bailoutMessage from '@/constants/bailoutMessage';
// Import Component
import baseFormMixin from '@/mixins/base-form-mixin';
@ -147,8 +141,7 @@ export default {
displayMatchedDifferentVehicleAlert: false,
displayMatchedTwoIdenticalYMMVehicleAlert: false,
previouslyEnteredCarId: '',
forwardButtonCarStyle: '',
displayNoServiceAlert: false
forwardButtonCarStyle: ''
};
},
computed: {
@ -203,10 +196,6 @@ export default {
selectedVehicleVin: {
handler() {
this.resetWarningsAndErrors();
if (!this.selectedVehicle?.vehicle.canSafeliteService) {
this.displayNoServiceAlert = true;
return this.$refs.siteFooter.disableForwardButton();
}
// does this vehicle match the previously selected carId?
this.isCarIdDifferent =
this.selectedVehicle?.vehicle.carId
@ -242,10 +231,19 @@ export default {
return false;
},
async forwardButtonAction() {
this.mainStore.resetBailout();
const vinLookup = await useMainStore().lookupVehicleByVin(this.selectedVehicle.vin);
if (!vinLookup) {
return;
}
if (vinLookup.data?.canSafeliteService === false) {
this.mainStore.setBailout(bailoutMessage.HeavyTruckVehicle(vinLookup.data.carId));
this.$router.navigate(
this.navigationScenarios.BAILOUT,
this.$route
);
return;
}
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(vinLookup.data.carId);
await useMainStore().saveVin(
{

View file

@ -204,7 +204,9 @@ export default {
methods: {
backButtonAction() {
// route to move backwards
this.mainStore.resetBailout();
if (this.bailout.bailoutCode !== BailoutCode.HeavyTruckVehicle) {
this.mainStore.resetBailout();
}
this.$router.navigateWithSpinner(
this.navigationScenarios.CLICKED_BACK_PREVIOUS,
this.$route

View file

@ -10,11 +10,69 @@ import { getRandomString, getRandomInt, getRandomBoolean } from '@/helpers/data-
import navigationScenarios from '@/router/router-constants/navigation-scenarios.js';
import { useMainStore } from '@/store/index.js';
/** @ignore */
function setupMocks({
storeData,
props
}) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn(),
navigateWithSpinner: jest.fn()
},
loadScript: jest.fn().mockResolvedValue()
});
if (storeData) mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: storeData
}
})];
window.google = {
maps: {
event: {
addListener: jest
.fn()
.mockImplementation((element, eventName, callbackFunction) => {
/** @ignore */
function interceptedCallbackFunction(e) {
callbackFunction(e.detail);
}
// selectedPlace = "Woogly";
element.addEventListener(eventName, interceptedCallbackFunction);
}),
removeListener: jest.fn(),
clearInstanceListeners: jest.fn()
},
places: {
Autocomplete: jest.fn().mockImplementation((el) => el),
AutocompleteService: jest.fn().mockImplementation(() => {
return {
getPlacePredictions: (request, callback) => {
// Throwing an error here guarantees that the function that calls it finishes
throw new Error();
}
};
}),
PlacesService: jest.fn().mockImplementation(() => {}),
AutocompleteSessionToken: jest.fn().mockImplementation(() => {})
}
}
};
if (props) mountOptions.propsData = props;
const wrapper = shallowMount(contactDetails, mountOptions);
return { wrapper };
}
describe('contactDetails.vue', () => {
describe('Rendering', () => {
test('Should render site header', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const siteHeader = wrapper.findComponent({ ref: 'siteHeader' });
@ -24,7 +82,7 @@ describe('contactDetails.vue', () => {
});
test('Should render sub title', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const siteSubHeader = wrapper.findComponent({ ref: 'siteSubHeader' });
@ -34,7 +92,7 @@ describe('contactDetails.vue', () => {
});
test('Should render appointment information alert', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const appointmentInformationAlert = wrapper.findComponent({ ref: 'appointmentInformationAlert' });
@ -44,8 +102,6 @@ describe('contactDetails.vue', () => {
});
test('Should render same as policy address question subcomponent if service zip is same as customer zip', async () => {
// Arrange
const mountOptions = getMountOptions();
const customerStreetAddress = getRandomString(10, 50);
const customerStreetAddress2 = getRandomString(0, 50);
const customerCity = getRandomString(4, 20);
@ -55,7 +111,7 @@ describe('contactDetails.vue', () => {
const serviceCity = getRandomString(4, 20);
const serviceState = getRandomString(2, 2);
const zipCode = getRandomInt(10000, 99999).toString();
const mainInitialState = {
const storeData = {
order: {
customer: {
address: {
@ -75,12 +131,7 @@ describe('contactDetails.vue', () => {
}
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({ storeData });
await wrapper.vm.$nextTick();
@ -92,8 +143,6 @@ describe('contactDetails.vue', () => {
});
test('Should not render same as policy address question subcomponent if service zip is different from customer zip', () => {
// Arrange
const mountOptions = getMountOptions();
const firstName = getRandomString(4, 15);
const lastName = getRandomString(4, 15);
const emailAddress = getRandomString(10, 20);
@ -101,7 +150,7 @@ describe('contactDetails.vue', () => {
const testZipCode = getRandomInt(10000, 99999);
const customerZipCode = testZipCode.toString();
const serviceZipCode = (testZipCode + 1).toString();
const mainInitialState = {
const storeData = {
order: {
customer: {
firstName,
@ -119,12 +168,7 @@ describe('contactDetails.vue', () => {
}
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({ storeData });
// Act
const sameAsPolicyAddressQuestion = wrapper.findComponent({ ref: 'sameAsPolicyAddressQuestion' });
@ -134,7 +178,7 @@ describe('contactDetails.vue', () => {
});
test('Should render address question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const addressQuestion = wrapper.findComponent({ ref: 'addressQuestion' });
@ -144,7 +188,7 @@ describe('contactDetails.vue', () => {
});
test('Should render apartment question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const address2Question = wrapper.findComponent({ ref: 'address2Question' });
@ -154,7 +198,7 @@ describe('contactDetails.vue', () => {
});
test('Should render city question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const cityQuestion = wrapper.findComponent({ ref: 'cityQuestion' });
@ -164,7 +208,7 @@ describe('contactDetails.vue', () => {
});
test('Should render state question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const stateQuestion = wrapper.findComponent({ ref: 'stateQuestion' });
@ -174,7 +218,7 @@ describe('contactDetails.vue', () => {
});
test('Should render zip code question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const zipCodeQuestion = wrapper.findComponent({ ref: 'zipCodeQuestion' });
@ -184,7 +228,7 @@ describe('contactDetails.vue', () => {
});
test('Should render change zip code alert', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const changeZipCodeAlert = wrapper.findComponent({ ref: 'changeZipCodeAlert' });
@ -194,7 +238,7 @@ describe('contactDetails.vue', () => {
});
test('Should render vehicle protected question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const vehicleProtectedQuestion = wrapper.findComponent({ ref: 'vehicleProtectedQuestion' });
@ -204,7 +248,7 @@ describe('contactDetails.vue', () => {
});
test('Should render technician notes textarea question subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const notesQuestion = wrapper.findComponent({ ref: 'notesQuestion' });
@ -214,7 +258,7 @@ describe('contactDetails.vue', () => {
});
test('Should render clearance text subcomponent', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const clearanceText = wrapper.findComponent({ ref: 'clearanceText' });
@ -224,7 +268,7 @@ describe('contactDetails.vue', () => {
});
test('Should render site footer', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions());
const { wrapper } = setupMocks({});
// Act
const footer = wrapper.findComponent({ ref: 'siteFooter' });
@ -234,8 +278,6 @@ describe('contactDetails.vue', () => {
});
test('Mocked store yields expected data', () => {
// Arrange
const mountOptions = getMountOptions();
const address = getRandomString(10, 50);
const address2 = getRandomString(0, 50);
const city = getRandomString(4, 20);
@ -243,7 +285,7 @@ describe('contactDetails.vue', () => {
const zipCode = getRandomInt(10000, 99999).toString();
const notesForTechnician = getRandomString(1, 100);
const isVehicleProtected = getRandomBoolean();
const mainInitialState = {
const storeData = {
order: {
serviceLocation: {
address,
@ -258,13 +300,8 @@ describe('contactDetails.vue', () => {
}
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({ storeData });
// Assert
expect(wrapper.vm.address).toBe(address);
@ -280,11 +317,7 @@ describe('contactDetails.vue', () => {
describe('Navigation', () => {
test('Back button clicked triggers navigation', () => {
// Arrange
const wrapper = shallowMount(contactDetails, getMountOptions({
router: {
navigateWithSpinner: jest.fn()
}
}));
const { wrapper } = setupMocks({});
wrapper.vm.navigateBack = baseMixin.methods.navigateBack;
// Act
@ -295,42 +328,45 @@ describe('contactDetails.vue', () => {
expect(wrapper.vm.$router.navigateWithSpinner)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_BACK, undefined);
});
test('Forward button clicked triggers appropriate navigation', () => {
test('Forward button clicked triggers appropriate navigation', async () => {
// Arrange
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
},
navigationScenarios
});
const mainInitialState = {
const storeData = {
order: {
serviceLocation: { IsSafeliteProvider: true }
}
};
mountOptions.global.plugins = [createTestingPinia({
initialState: {
main: mainInitialState
}
})];
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({ storeData });
// Act
wrapper.vm.forwardButtonAction();
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
expect(wrapper.vm.$router.navigate)
.toHaveBeenCalledWith(navigationScenarios.CLICKED_FORWARD, undefined);
});
test('Forward button click updates service location info', () => {
test('Forward button click updates service location info', async () => {
// Arrange
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
const provider = {
address: {
city: getRandomString(4, 20),
state: getRandomString(2, 2),
streetAddress: getRandomString(10, 50),
zipCode: getRandomInt(10000, 99999).toString(),
zipCodeCtu: getRandomInt(10000, 99999).toString()
},
companyName: getRandomString(5, 15),
phoneNumber: getRandomInt(1000000000, 9999999999).toString(),
providerNumber: getRandomInt(100000, 999999).toString()
}
const storeData = {
order: {
serviceLocation: {
provider
}
}
});
const wrapper = shallowMount(contactDetails, mountOptions);
};
const { wrapper } = setupMocks({ storeData });
const address = getRandomString(10, 50);
const address2 = getRandomString(0, 50);
const city = getRandomString(4, 20);
@ -347,32 +383,28 @@ describe('contactDetails.vue', () => {
});
// Act
wrapper.vm.forwardButtonAction();
await wrapper.vm.forwardButtonAction();
// Assert
expect(useMainStore().updateServiceLocation).toHaveBeenCalledWith({
address,
address2,
city,
isVehicleProtected
isVehicleProtected,
provider
});
});
test('Forward button click updates notes for technician', () => {
test('Forward button click updates notes for technician', async () => {
// Arrange
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
}
});
const wrapper = shallowMount(contactDetails, mountOptions);
const { wrapper } = setupMocks({});
const notesForTechnician = getRandomString(1, 100);
wrapper.setData({
notesForTechnician
});
// Act
wrapper.vm.forwardButtonAction();
await wrapper.vm.forwardButtonAction();
// Assert
expect(useMainStore().updateContactInfo).toHaveBeenCalledWith({

View file

@ -133,6 +133,7 @@ import errorMessages from '@/constants/error-messages';
import { required } from '@/helpers/validation-rules';
import widgetFields from '@/constants/cms-widget-fields';
import states from '@/constants/states';
import applicationConfig from '@/constants/application-config';
// DEFINE VALIDATION RULES
defineRule('street-address-required', required(errorMessages.SERVICE_ADDRESS_REQUIRED));
@ -157,8 +158,9 @@ export default {
async beforeRouteEnter(to, from, next) {
const cmsContent = await fetchCmsContentForPage(to.query.issPage);
next((vm) => {
next(async (vm) => {
vm.setCmsContent(cmsContent);
await vm.setupAddressLookup();
});
},
data() {
@ -239,17 +241,22 @@ export default {
/**
* @summary Steps to perform when forward button clicked.
*/
forwardButtonAction() {
async forwardButtonAction() {
const contactInfo = {
notesForTechnician: this.notesForTechnician
};
useMainStore().updateContactInfo(contactInfo);
const provider = useMainStore().serviceLocation.provider;
await this.geocodeAddress();
useMainStore().updateServiceLocation({
address: this.address,
address2: this.address2,
city: this.city,
isVehicleProtected: this.isVehicleProtected || 'false'
isVehicleProtected: this.isVehicleProtected || 'false',
provider
});
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
@ -264,7 +271,100 @@ export default {
this.address = '';
this.address2 = '';
this.city = '';
}
},
async setupAddressLookup() {
const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY;
await this.$loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&callback=Function.prototype`)
.catch(() => {
// Failed to fetch script
window.console.warn('Unable to load Google Places API script');
});
},
geocodeAddress() {
const autoCompletePromise = new Promise((resolve, reject) => {
try {
// Get Autocomplete Service
const acService = new window.google.maps.places.AutocompleteService();
// Get Places Service, needs pseudo element (or a map)
const placeService = new window.google.maps.places.PlacesService(document.createElement('div'));
// Create Autocomplete Session token, multiple requests one pricing hit
const acSessionToken = new window.google.maps.places.AutocompleteSessionToken();
const addressValue = `${this.address}, ${this.city}, ${this.state} ${this.zipCode}`;
acService.getPlacePredictions(
{
input: addressValue,
type: ['geocode'],
componentRestrictions: { country: ['us'] },
sessionToken: acSessionToken
},
(predictions) => {
if (predictions && predictions.length > 0) {
const firstPrediction = predictions[0];
if (firstPrediction.place_id) {
placeService.getDetails(
{
placeId: firstPrediction.place_id,
fields: ['address_components'],
sessionToken: acSessionToken
},
(details) => {
this.fillInAddress(details);
resolve();
}
);
} else {
resolve();
}
} else {
resolve();
}
}
);
} catch (error) {
window.console.warn('Error initializing Google Places API services', error);
resolve();
}
});
return autoCompletePromise;
},
async fillInAddress(googlePlace) {
let processedStreetAddress = false;
let processedRoute = false;
for (const component of googlePlace.address_components) {
const componentType = component.types[0];
switch (componentType) {
case 'street_number': {
if (processedRoute) {
this.address = `${component.long_name} ${this.address}`;
} else {
this.address = component.long_name;
}
processedStreetAddress = true;
break;
}
case 'route': {
if (processedStreetAddress) {
this.address += ` ${component.short_name}`;
} else {
this.address = component.short_name;
}
processedRoute = true;
break;
}
case 'locality': {
this.city = component.long_name;
break;
}
default:
}
}
},
}
};
</script>

View file

@ -3,6 +3,7 @@
exports[`coverageStatement.vue returns the initial data 1`] = `
Object {
"CANCEL_CLAIM_REF_NAME": "CancelClaimModal",
"OEM_ENDORSEMENT_REF_NAME": "OEMModal",
"RECAL_MODAL_REF_NAME": "RecalModal",
"baseServiceLineItems": Array [],
"isPageLoading": true,

View file

@ -6,18 +6,25 @@
<modal
ref="cancelClaimModal"
:headerText="modalHeaderText"
:footerButtonText="modalFooterText"
buttonVariant="link"
@isModalOpened="setModalStatus"
@footerButtonEvent="cancelClaimConfirmation">
:displayFooterButton="false">
<div v-html="modalBodyText"></div>
<buttonMain
id="btnContinueReferral"
variant="success"
:buttonText="modalBodyText2"
class="mt-4 w-100"
class="mt-5 w-100"
@clickEvent="returnToClaim">
</buttonMain>
<div class="text-center mt-5">
<textLink
class="cancel-link"
linkType="text"
text="No, I want to cancel"
href="#!"
@clickEvent="cancelClaimConfirmation">
</textLink>
</div>
</modal>
</div>
</transition>
@ -26,12 +33,14 @@
<script>
import modal from '@/digital-components/modal/modal.vue';
import ButtonMain from '@/ux-components/button-main/button-main.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
export default {
name: 'cancel-claim-modal',
components: {
ButtonMain,
modal
modal,
textLink
},
emits: ['cancel-claim-confirmation', 'return-to-claim'],
data() {
@ -74,5 +83,18 @@ export default {
</script>
<style lang="scss" scoped>
:deep(.modal) {
.modal-title {
margin-top: 1rem;
}
.cancel-link {
text-decoration: underline;
}
.modal-footer {
padding-bottom: 1rem;
}
.modal-body {
color: #4d4e53;
}
}
</style>

View file

@ -10,7 +10,7 @@
cmsWidgetName="SiteHeaderWidget" />
</div>
<div class="iss-heritage-container-width">
<div v-if="!isPageLoading"
<div v-show="!isPageLoading"
class="coverage-statement-container iss-heritage-content-container-width">
<h5
ref="siteSubHeader"
@ -101,14 +101,16 @@
</div>
</div>
</div>
<contentGroupModal
<recalModal
:ref="RECAL_MODAL_REF_NAME"
cssModalHeadlineClass="text-center"
cmsWidgetName="RecalModal" />
<cancelClaimModal
:ref="CANCEL_CLAIM_REF_NAME"
@cancelClaimConfirmation="cancelClaim"
@returnToClaim="navigateForward" />
<oemEndorsementModal
:ref="OEM_ENDORSEMENT_REF_NAME"
cmsWidgetName="OEMEndorsementModal" />
</Form>
</template>
@ -116,13 +118,14 @@
// Import Component
import { Form } from 'vee-validate';
import siteHeader from '@/iss-components/site-header/site-header.vue';
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
import cancelClaimModal from '@/layouts/coverage-statement/cancel-claim-modal/cancel-claim-modal.vue';
import textBlock from '@/digital-components/text-block/text-block.vue';
import textLink from '@/ux-components/text-link/text-link.vue';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import afterpayModalBanner from '@/layouts/coverage-statement/afterpay-modal-banner/afterpay-modal-banner.vue';
import recalModal from '@/iss-components/recal-modal/recal-modal.vue';
// Import Supporting Files
import {
fetchCmsContentForPage,
@ -143,22 +146,25 @@ import showIssLoadingModal from '@/helpers/loading-modal-helper';
import { getPriceOfLineItems } from '@/helpers/price-calculator';
import coverageStatuses from '@/constants/coverage-statuses';
import coverageType from '@/constants/coverage-type';
import oemEndorsementModal from '@/layouts/coverage-statement/oem-endorsement-modal/oem-endorsement-modal.vue';
const RECAL_MODAL_REF_NAME = 'RecalModal';
const CANCEL_CLAIM_REF_NAME = 'CancelClaimModal';
const OEM_ENDORSEMENT_REF_NAME = 'OEMModal';
export default {
name: 'coverage-statement',
components: {
siteHeader,
Form,
contentGroupModal,
buttonMain,
textBlock,
textLink,
siteFooter,
cancelClaimModal,
afterpayModalBanner
afterpayModalBanner,
recalModal,
oemEndorsementModal
},
mixins: [baseFormMixin, vehicleQuestionsMixin],
async beforeRouteEnter(to, from, next) {
@ -221,7 +227,8 @@ export default {
nextStep: 'NextStepsWidget'
},
RECAL_MODAL_REF_NAME,
CANCEL_CLAIM_REF_NAME
CANCEL_CLAIM_REF_NAME,
OEM_ENDORSEMENT_REF_NAME
};
},
computed: {
@ -356,6 +363,7 @@ export default {
nextStepsBody(newValue, oldValue) {
if (newValue !== oldValue) {
setupModalLink(this, RECAL_MODAL_REF_NAME);
setupModalLink(this, OEM_ENDORSEMENT_REF_NAME);
}
}
},
@ -424,6 +432,7 @@ export default {
},
getCustomValueFromString(str) {
const { isRepair } = useMainStore().damage;
switch (str) {
case 'coverageUnverified':
return this.isUnverifiedVisible;
@ -449,6 +458,8 @@ export default {
return !isRepair && this.isDeductibleVisible && this.deductibleValue <= 0;
case 'verifiedReplaceDeductibleOverZero':
return !isRepair && this.isDeductibleVisible && this.deductibleValue > 0;
case 'hasOEMEndorsement':
return this.mainStore.hasOemEndorsement;
default:
return null;
}
@ -462,7 +473,7 @@ export default {
},
cancelClaim() {
this.mainStore.updateIsSafeliteProvider(false);
this.$router.navigateBailout(bailoutMessage.RequestCallback());
this.$router.navigateBailout(bailoutMessage.CoverageCancelledByUser());
}
}
};
@ -622,8 +633,4 @@ export default {
}
}
}
.modal {
overflow: hidden;
}
</style>

View file

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`returns the initial data 1`] = `Object {}`;

View file

@ -0,0 +1,126 @@
import { shallowMount } from '@vue/test-utils';
import oemEndorsementModal from '@/layouts/coverage-statement/oem-endorsement-modal/oem-endorsement-modal.vue';
const mockDataStrings = {
cmsBodyText: `Some Body Text from CMS`,
cmsHeaderText: 'Some Header Text from CMS',
cmsFooterText: 'Some Footer Text from CMS'
};
const mockCmsContent = {
BodyText: mockDataStrings.cmsBodyText,
HeaderText: mockDataStrings.cmsHeaderText,
FooterText: mockDataStrings.cmsFooterText
};
const mockMixin = {
methods: {
getCmsContent: jest.fn((_, cmsFieldName) => mockCmsContent[cmsFieldName])
},
};
function setupMocks() {
const wrapper = shallowMount(oemEndorsementModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: 'OEMEndorsementModal'
},
attachTo: document.body
});
return { wrapper };
}
test('returns the initial data', () => {
// Arrange
const { wrapper } = setupMocks();
// Assert
expect(wrapper.vm.$data).toMatchSnapshot();
});
describe('Computed', () => {
test('Should get modal header text from Widget CMS', () => {
// Arrange
const { wrapper } = setupMocks();
// Act
const result = wrapper.vm.modalHeaderText;
// Assert
expect(result).toBe(mockDataStrings.cmsHeaderText);
});
test('Should get modal close button text from Widget CMS', () => {
// Arrange
const { wrapper } = setupMocks();
// Act
const result = wrapper.vm.modalCloseButtonText;
// Assert
expect(result).toBe(mockDataStrings.cmsFooterText);
});
test('Should call getCmsContent with the correct widget name and field name for body text', () => {
// Arrange
const { wrapper } = setupMocks();
// Act
wrapper.vm.modalBodyText;
// Assert
expect(mockMixin.methods.getCmsContent).toHaveBeenCalledWith('OEMEndorsementModal', 'BodyText');
});
test('Should call getCmsContent with the correct widget name and field name for header text', () => {
// Arrange
const { wrapper } = setupMocks();
// Act
wrapper.vm.modalHeaderText;
// Assert
expect(mockMixin.methods.getCmsContent).toHaveBeenCalledWith('OEMEndorsementModal', 'HeaderText');
});
test('Should call getCmsContent with the correct widget name and field name for footer text', () => {
// Arrange
const { wrapper } = setupMocks();
// Act
wrapper.vm.modalCloseButtonText;
// Assert
expect(mockMixin.methods.getCmsContent).toHaveBeenCalledWith('OEMEndorsementModal', 'FooterText');
});
});
describe('Props', () => {
test('Should use the default cmsWidgetName prop value of OEMEndorsementModal', () => {
// Arrange
const wrapper = shallowMount(oemEndorsementModal, {
mixins: [mockMixin],
attachTo: document.body
});
// Assert
expect(wrapper.vm.cmsWidgetName).toBe('OEMEndorsementModal');
});
test('Should accept a custom cmsWidgetName prop', () => {
// Arrange
const customWidgetName = 'CustomOEMModal';
const wrapper = shallowMount(oemEndorsementModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: customWidgetName
},
attachTo: document.body
});
// Assert
expect(wrapper.vm.cmsWidgetName).toBe(customWidgetName);
});
});

View file

@ -0,0 +1,79 @@
<template>
<div class="recal-modal-container">
<modal
ref="oemEndorsementModal"
:footerButtonText="modalCloseButtonText"
@footerButtonEvent="footerButtonClick">
<h5
class="header-text"
v-html="modalHeaderText"></h5>
<div v-html="modalBodyText" class="oem-endorsement-modal-body"></div>
</modal>
</div>
</template>
<script>
import modal from '@/digital-components/modal/modal.vue';
export default {
name: 'oem-endorsement-modal',
components: {
modal
},
props: {
cmsWidgetName: {
type: String,
default: 'OEMEndorsementModal'
}
},
computed: {
modalBodyText() {
return this.getCmsContent(this.cmsWidgetName, 'BodyText');
},
modalHeaderText() {
return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
},
modalCloseButtonText() {
return this.getCmsContent(this.cmsWidgetName, 'FooterText');
},
},
methods: {
openModal() {
this.$refs.oemEndorsementModal.openModal();
},
footerButtonClick() {
this.$refs.oemEndorsementModal.closeModal();
},
}
};
</script>
<style lang="scss" scoped>
:deep(.modal) {
&.modal-component {
.modal-dialog {
.modal-content {
.modal-body {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
}
}
.modal-footer {
button {
&:hover {
background-color: $heritage-blue-primary;
color: $white;
}
}
}
}
.oem-endorsement-modal-body {
padding: 10px 0 10px 0;
p:first-child {
margin-bottom: 10px;
}
}
}
</style>

View file

@ -151,68 +151,6 @@ describe('license-plate-lookup.vue', () => {
// Assert
expect(wrapper.findComponent({ ref: 'alertVinNotFound' }).isVisible()).toBe(true);
});
test('non-serviceable big truck found, display NoServiceAlert', async () => {
// Arrange
const mockRegistrationLicensePlate = {
licensePlate: 'BIGTRUCKTEST'
};
const { wrapper } = setupMocks({
lookupVinByPlateResponse: {
vin: 'BIGTRUCKVIN',
vehicle: {
carId: 'BIGTRUCKTESTCARID',
isBigTruck: true,
canSafeliteService: false
}
}
});
useMainStore().order.vehicle.carId = 'BIGTRUCKTESTCARID';
await wrapper.setData({
licensePlate: mockRegistrationLicensePlate
});
wrapper.vm.navigateForward = jest.fn();
// Act
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.findComponent({ ref: 'alertNoService' }).isVisible()).toBe(true);
});
test('serviceable big truck found, do not display NoServiceAlert', async () => {
// Arrange
const mockRegistrationLicensePlate = {
licensePlate: 'BIGTRUCKTEST2'
};
const { wrapper } = setupMocks({
lookupVinByPlateResponse: {
vin: 'BIGTRUCKVIN2',
vehicle: {
carId: 'BIGTRUCKTESTCARID2',
isBigTruck: true,
canSafeliteService: true
}
}
});
useMainStore().order.vehicle.carId = 'BIGTRUCKTESTCARID2';
await wrapper.setData({
licensePlate: mockRegistrationLicensePlate
});
wrapper.vm.navigateForward = jest.fn();
// Act
await wrapper.vm.forwardButtonAction();
// Assert
expect(wrapper.findComponent({ ref: 'alertNoService' }).exists()).toBe(false);
});
});
describe('navigation', () => {

View file

@ -59,13 +59,6 @@
:manualCopy="AlertMatchedTwoIdenticalYMMVehicleBody"
alertClass="alert-warning"
:isDismissible="false" />
<alert
v-if="displayNoServiceAlert"
ref="alertNoService"
class="mt-5"
cmsWidgetName="AlertNoServiceWidget"
alertClass="alert-danger"
:isDismissable="false" />
<siteFooter
ref="siteFooter"
class="mt-5"
@ -156,8 +149,7 @@ export default {
isCarIdDifferent: false,
customAlertData: {},
isSelectedGlassAvailableForVehicle: true,
forwardButtonCarStyle: '',
displayNoServiceAlert: false
forwardButtonCarStyle: ''
};
},
computed: {
@ -247,6 +239,7 @@ export default {
// NOTE: If form is not valid, this method is not called when 'Continue' button is clicked
async forwardButtonAction() {
this.resetWarningsAndErrors();
this.mainStore.resetBailout();
// Lookup VIN
const vinLookupResponse = await useMainStore().lookupVinByPlate(
@ -265,9 +258,11 @@ export default {
// Vehicle found from VIN lookup
const vehicleFromLookup = vinLookupResponse.data.vehicle;
if (!vehicleFromLookup.canSafeliteService) {
this.displayNoServiceAlert = true;
showIssLoadingModal(false);
return this.$refs.siteFooter.disableForwardButton();
this.mainStore.setBailout({
type: 'HeavyTruckVehicle',
carId: vehicleFromLookup.carId
});
return this.navigateForward();
}
// Check if the CarId has changed
@ -326,7 +321,6 @@ export default {
resetWarningsAndErrors() {
this.displayVinNotFoundAlert = false;
this.displayMatchedDifferentVehicleAlert = false;
this.displayNoServiceAlert = false;
}
}
};

View file

@ -281,11 +281,13 @@ export default {
},
serviceTypeText() {
const glassPieces = this.submittedOrder.lineItems.glassParts?.map((part) => part.partType.toLowerCase()) ?? [];
let glassList = getGlassList(glassPieces);
let glassList = '';
let workType = '';
if (this.isRepair) {
glassList = 'windshield';
workType = 'repair';
} else {
glassList = getGlassList(glassPieces);
workType = 'replacement';
if (this.hasRecalibrationPart) {
if (glassList === 'windshield') {
@ -622,7 +624,7 @@ export default {
}
.cart-container {
padding: 0 .9375rem;
padding: 0 .9375rem 2.1875rem;
}
.header {

View file

@ -45,7 +45,8 @@ function setupMocks({ customMountOptions = {}, queryString }, mainInitialState =
}
return 'false';
})
}),
savePageDataToStore: jest.fn()
}
};
const cmsMixin = {
@ -84,33 +85,10 @@ describe('payment-method.vue', () => {
const wrapper = setupMocks({}, store);
// Act
const paymethod = wrapper.vm.getPaymentMethodFromStore();
wrapper.vm.initializeComponent();
// Assert
expect(paymethod).toBe(payLaterMethod);
});
test('getting payment method when method is pay in advance', async () => {
// Arrange
const payInAdvanceMethod = paymentMethods.CREDIT_CARD;
const store = {
order: {
payment: {
paymentMethod: payInAdvanceMethod
},
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
},
currentDeductible: 1000
}
};
const wrapper = setupMocks({}, store);
// Act
const paymethod = wrapper.vm.getPaymentMethodFromStore();
// Assert
expect(paymethod).toBe(payInAdvanceMethod);
expect(wrapper.vm.paymentMethod).toBe(payLaterMethod);
});
test('getting PAY_AT_TIME_OF_SERVICE when deductible is 0', async () => {
// Arrange
@ -124,16 +102,18 @@ describe('payment-method.vue', () => {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
},
currentDeductible: 0
currentDeductible: {
replace: 0
}
}
};
const wrapper = setupMocks({}, store);
// Act
const paymethod = wrapper.vm.getPaymentMethodFromStore();
wrapper.vm.initializeComponent();
// Assert
expect(paymethod).toBe(payInAdvanceMethod);
expect(wrapper.vm.paymentMethod).toBe(payInAdvanceMethod);
});
test('getting PAY_AT_TIME_OF_SERVICE when Unverified', async () => {
// Arrange
@ -147,16 +127,18 @@ describe('payment-method.vue', () => {
coverageStatus: coverageStatuses.PENDING,
coverageType: coverageType.Deductible
},
currentDeductible: 1000
currentDeductible: {
replace: 1000
}
}
};
const wrapper = setupMocks({}, store);
// Act
const paymethod = wrapper.vm.getPaymentMethodFromStore();
wrapper.vm.initializeComponent();
// Assert
expect(paymethod).toBe(payInAdvanceMethod);
expect(wrapper.vm.paymentMethod).toBe(payInAdvanceMethod);
});
test('getting PAY_AT_TIME_OF_SERVICE when PIA Experiment disabled', async () => {
// Arrange
@ -180,16 +162,18 @@ describe('payment-method.vue', () => {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
},
currentDeductible: 1000
currentDeductible: {
replace: 1000
}
}
};
const wrapper = setupMocks({}, store, mixin);
// Act
const paymethod = wrapper.vm.getPaymentMethodFromStore();
wrapper.vm.initializeComponent();
// Assert
expect(paymethod).toBe(payInAdvanceMethod);
expect(wrapper.vm.paymentMethod).toBe(payInAdvanceMethod);
});
});

View file

@ -178,6 +178,7 @@ export default {
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const pageData = useMainStore().pageData(to.query.issPage);
const promiseResultMap = [
{
@ -192,7 +193,7 @@ export default {
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.initializeComponent();
vm.initializeComponent(pageData);
vm.updateFooterButtonText(vm.customCallToActionButtonCopy);
});
},
@ -238,8 +239,23 @@ export default {
return !isEnabled || useMainStore().isUnverified || (useMainStore().isDeductible && isDeductibleTotalEqualToZero);
},
isAdyenEnabled() {
const adyenEnabled = this.getSettingValue(experimentSettings.ISS_ENABLE_ADYEN_V1);
return adyenEnabled === 'true';
},
paymentMethodWidgetName() {
return supportsApplePay() ? this.widget.paymentMethodApplePay : this.widget.paymentMethod;
let showApplePay = false;
if (this.isAdyenEnabled) {
// Apple Pay is Available in Adyen if the protocol is HTTPS
if (window.location.protocol === 'https:') {
showApplePay = true;
}
}
else {
showApplePay = supportsApplePay();
}
return showApplePay ? this.widget.paymentMethodApplePay : this.widget.paymentMethod;
},
showCartTotal() {
return this.mainStore.isVerified && (this.mainStore.isITAC || this.mainStore.isNoComp
@ -382,26 +398,21 @@ export default {
&& customerReqs
&& contactInfoReqs);
},
initializeComponent() {
const paymentMethodFromStore = this.getPaymentMethodFromStore();
// SMS Opt In defaults to 'no', but we don't actually want to use it if the user hasn't saved data form this page yet
if (paymentMethodFromStore) {
this.smsOptIn = this.getSMSOptInFromStore();
initializeComponent(pageData) {
if (pageData) {
this.smsOptIn = pageData.smsOptIn;
}
this.smsPhoneNumber = this.getSMSPhoneFromStore();
},
getPaymentMethodFromStore() {
if (this.isPayInAdvanceDisabled) {
return paymentMethods.PAY_AT_TIME_OF_SERVICE;
this.paymentMethod = paymentMethods.PAY_AT_TIME_OF_SERVICE;
}
return useMainStore().order.payment.paymentMethod;
},
updateFooterButtonText(newValue) {
this.$refs.siteFooter.updateButtonText(newValue);
},
async forwardButtonAction() {
this.mainStore.savePaymentMethodChoice(this.paymentMethod);
this.savePageDataToStore(issPageValues.PAYMENT_METHOD, { smsOptIn: this.smsOptIn });
if (!this.hideSMSOptIn) {
const requestTextUpdates = this.smsOptIn === 'Yes';
this.mainStore.updateContactInfo({ requestTextUpdates });
@ -422,8 +433,12 @@ export default {
bailoutOnError: true
});
const scenario = this.isAdyenEnabled
? this.navigationScenarios.CLICKED_PAY_NOW_ADYEN
: this.navigationScenarios.CLICKED_PAY_NOW;
this.$router.navigate(
this.navigationScenarios.CLICKED_PAY_NOW,
scenario,
this.$route,
{},
{ [routerParams.SKIP_SAVE_SESSION]: true }
@ -439,17 +454,17 @@ export default {
this.$route
);
},
getSMSOptInFromStore() {
return this.mainStore.contactInfo.requestTextUpdates ? 'Yes' : 'No';
},
getSMSPhoneFromStore() {
return this.mainStore.contactInfo.alternativePhone ?? this.mainStore.contactInfo.servicePhone;
},
handleEditClicked(section) {
switch (section) {
case 'location':
const scenario = this.mainStore.isMobileAppointment
? this.navigationScenarios.EDIT_SERVICE_LOCATION_MOBILE
: this.navigationScenarios.EDIT_SERVICE_LOCATION_INSHOP;
this.$router.navigateWithSpinner(
this.navigationScenarios.EDIT_SERVICE_LOCATION,
scenario,
this.$route
);
break;

View file

@ -0,0 +1,623 @@
<template>
<Form>
<div class="fade-on-route-transition">
<div class="justify-content-center">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
<div class="iss-heritage-container-width payment-page-container">
<div id="card-container" class="row">
<div class="col-lg-8">
<alert
v-if="hasPaymentFailureError"
name="payInAdvanceErrorAlert"
class="pay-in-advance-alert"
cmsWidgetName="PayInAdvanceErrorAlertWidget"
alertClass="alert-danger"
:isDismissible="false"
:isCollapsible="true"
:preventDefaultOnClick="true"
@textLinkClicked="paymentFailedPayLater" />
<div id="adyen-container"></div>
</div>
<div class="col-lg-4 col-md-10 col-xs-12">
<div class="order-summary-panel">
<div class="cart-title">{{ cartTitle }}</div>
<cartDropdown
ref="cart"
class="cart-dropdown-component"
:readOnly="true"
:showAsPaid="false"
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle" />
</div>
</div>
</div>
<div class="col-xs-12">
<siteFooter
class="footer-component"
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="true"
:isForwardButtonHidden="true"
@ForwardClicked="forwardButtonAction"
@backClicked="backButtonAction" />
</div>
</div>
</div>
</Form>
</template>
<script>
// Components
import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import alert from '@/ux-components/alert/alert.vue';
import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
// Supporting files
import issPageValues from '@/router/router-constants/issPage-values.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { getCartTotal } from '@/helpers/cart-helper';
import { useMainStore } from '@/store';
import widgetFields from '@/constants/cms-widget-fields.js';
import { paymentMethods } from '@/constants/payment-method-constants.js';
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
import queryStrings from '@/constants/query-strings';
import { formatAmountInDollars } from '@/helpers/text-helper';
import showIssLoadingModal from '@/helpers/loading-modal-helper';
import { submitWorkOrder } from '@/helpers/order-helper';
import submitType from '@/constants/submit-type';
import "@adyen/adyen-web/styles/adyen.css";
import { mapAdyenToIssPaymentMethod, mapIssToAdyenPaymentMethod } from '@/helpers/adyen-helper';
import { createAdyenCheckout } from "@/helpers/adyen-helper";
import { Dropin } from "@adyen/adyen-web/auto";
import applicationConfig from '@/constants/application-config';
export default {
name: 'payment-page-adyen',
components: {
cartDropdown,
siteHeader,
siteFooter,
Form,
alert
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const alertToDisplay = to.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT];
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: 'cmsContent',
promise: cmsContentPromise
}
];
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
vm.$nextTick(() => {
if (vm.$refs.cart) {
const cartItems = vm.$refs.cart.cartItems;
vm.getPayInAdvanceLineItems(cartItems);
}
if (alertToDisplay) {
vm.hasPaymentFailureError = true;
}
});
});
},
mounted() {
showIssLoadingModal(true);
this.initializeAdyen().finally(() => {
showIssLoadingModal(false);
});
},
data() {
return {
referralSequenceNumber: useMainStore().order.referralSequenceNumber,
emailAddress: useMainStore().contactInfo.emailAddress,
address1: this.getAddress1(),
address2: this.getAddress2(),
city: this.getCity(),
state: this.getState(),
zipCode: this.getZipCode(),
firstName: useMainStore().contactInfo.firstName,
lastName: useMainStore().contactInfo.lastName,
phoneNumber: useMainStore().contactInfo.servicePhone,
ctu: useMainStore().order.serviceLocation.provider?.address?.zipCodeCtu ?? useMainStore().order.serviceLocation.zipCodeCtu,
referralCorrelationId: useMainStore().order.referralCorrelationId,
workOrderNumber: this.getWorkOrderNumber(),
invoiceNumber: this.getInvoiceNumber(),
totalAmount: this.getAmountDue(),
displayAmount: this.getDisplayAmountDue(),
payInAdvanceLineItems: '',
sessionId: '',
dropinComponent: null,
sourceSystem: 'ISS-NextGen',
hasPaymentFailureError: false,
adyenTimeout: null
};
},
unmounted() {
if (this.adyenTimeout) {
clearTimeout(this.adyenTimeout);
}
},
computed: {
piaType() {
return this.mainStore.payment.paymentMethod;
},
parentDomainName() {
return window.location.hostname;
},
payInAdvanceResponseUrl() {
const { protocol, host } = window.location;
return `${protocol}//${host}/?issPage=${issPageValues.PAYMENT_RETURN_ADYEN}&src=iss-nextgen`;
},
payInAdvanceCancelUrl() {
const { protocol, host } = window.location;
return `${protocol}//${host}/?issPage=${issPageValues.PAYMENT_METHOD}&src=iss-nextgen`;
},
cartTitle() {
return this.getCmsContent('OrderSummaryTextWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
switchPaymentText() {
return this.getCmsContent('SwitchPaymentTextWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
submitPaymentText() {
return this.getCmsContent('SiteFooterWidget', widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT);
},
adyenPriceTotal() {
return Math.round(this.totalAmount * 100);
},
workOrderNumberLastSixDigits() {
const workOrderNumber = this.workOrderNumber ?? "";
const tokens = workOrderNumber.split("-");
const lastSegment = tokens[tokens.length - 1] ?? "";
const numDigits = lastSegment.length;
const numZeroes = 6 - numDigits;
let result = "";
for (let i = 0; i < numZeroes; i++) {
result += "0";
}
result += lastSegment;
return result;
},
splitStreetAddress() {
const address = this.address1;
const tokens = address.split(" ") ?? [""];
const number = tokens[0];
const street = tokens.slice(1).join(" ") ?? "";
return {
number: number ?? "",
street: street ?? "",
};
}
},
methods: {
arePagePrerequisitesValid() {
// Line Items
const packageReqs =
!!useMainStore().order.lineItems.supportingItems;
// Service Location
const { serviceLocation } = useMainStore().order;
const mobileReqs = !!(
serviceLocation.address
&& serviceLocation.city
&& serviceLocation.state
&& serviceLocation.zipCode
);
const providerLocation = serviceLocation.provider.address;
const dropOffInshopReqs = !!(
providerLocation.streetAddress
&& providerLocation.city
&& providerLocation.state
&& providerLocation.zipCode
);
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|| serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
const serviceLocationReqs =
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
// Insurance
const isInsuranceSet =
useMainStore().order.payment.isInsurance !== null;
// Schedule
const { schedule } = useMainStore().order;
const scheduleReqs = !!(
schedule.date
&& schedule.startTime
&& schedule.endTime
&& schedule.jobMaxMinutes
&& schedule.jobMinMinutes
);
// Contact Info
const { contactInfo } = useMainStore();
const contactInfoReqs = !!(
contactInfo.firstName
&& contactInfo.lastName
&& contactInfo.servicePhone
&& contactInfo.emailAddress
);
const paymentMethodReqs = useMainStore().isPayInAdvance;
return (
packageReqs
&& serviceLocationReqs
&& isInsuranceSet
&& scheduleReqs
&& contactInfoReqs
&& paymentMethodReqs
);
},
getWorkOrderNumber() {
const { workOrderNumber } = useMainStore().order;
if (workOrderNumber) {
const items = workOrderNumber.split('-');
if (items.length > 1) {
return items[1];
}
}
return undefined;
},
getInvoiceNumber() {
const { workOrderNumber } = useMainStore().order;
if (workOrderNumber) {
return workOrderNumber.replace('-', '');
}
return undefined;
},
getAddress1() {
return (
useMainStore().order.serviceLocation.address
?? useMainStore().order.serviceLocation.provider.address
.streetAddress
);
},
getAddress2() {
return useMainStore().order.serviceLocation.address2;
},
getCity() {
return (
useMainStore().order.serviceLocation.city
?? useMainStore().order.serviceLocation.provider.address.city
);
},
getState() {
return (
useMainStore().order.serviceLocation.state
?? useMainStore().order.serviceLocation.provider.address.state
);
},
getZipCode() {
return (
useMainStore().order.customer.address.zipCode
?? useMainStore().order.serviceLocation.zipCode
?? useMainStore().order.serviceLocation.provider.address.zipCode
);
},
getPayInAdvanceLineItems(cartItems) {
const { glassParts } = useMainStore().order.lineItems;
const lineItems =
glassParts === null
? [
this.getPayInAdvanceFormattedLineItem('Labor', 0, 1),
this.getPayInAdvanceFormattedLineItem('Repair supplies', 0, 1)
]
: [
this.getPayInAdvanceFormattedLineItem('Parts and labor', 0, 1)
];
cartItems.forEach((item) => {
if (item.name !== null && item.category !== 'promos') {
lineItems.push(this.getPayInAdvanceFormattedLineItem(
item.name,
(item.salesTax + item.subTotal).toFixed(2),
1
));
}
});
this.payInAdvanceLineItems = lineItems.join('||');
},
getPayInAdvanceFormattedLineItem(itemName, price, quantity) {
return `${itemName}|${price}|${quantity}`;
},
getAmountDue() {
return getCartTotal(useMainStore().order);
},
getDisplayAmountDue() {
return formatAmountInDollars(this.getAmountDue());
},
async paymentFailedPayLater() {
this.showIssLoadingModal(true);
this.mainStore.savePaymentMethodChoice(paymentMethods.PAY_AT_TIME_OF_SERVICE);
await submitWorkOrder({ submitType: submitType.SAFELITE });
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
backButtonAction() {
// page will not render with normal navigation due to navigating from the iframe
window.location = this.payInAdvanceCancelUrl;
},
forwardButtonAction() {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
async initializeAdyen() {
const requestBody = this.getAdyenInitRequestInfo();
const adyenResponse = await this.mainStore.initializeAdyenPayment(requestBody);
const session = {
id: adyenResponse.sessionId,
sessionData: adyenResponse.sessionData,
};
this.sessionId = adyenResponse.sessionId;
const checkout = await createAdyenCheckout({
session: session,
handlers: {
onPaymentCompleted: (result, component) => {
this.handleCompletedPayment(result);
},
onPaymentFailed: (result, component) => {
this.handleFailedPayment(result);
},
onError: (error, component) => {
this.handleError(error);
},
},
options: {
amount: this.adyenPriceTotal,
},
});
const expiryTime = new Date(checkout.options.expiresAt);
const expiryInterval = expiryTime.getTime() - new Date().getTime();
const handleTimeout = () => {
this.resetAdyenDropin();
};
if (this.adyenTimeout) {
clearTimeout(this.adyenTimeout);
}
this.adyenTimeout = setTimeout(handleTimeout, expiryInterval);
const configuration = {
paymentMethodsConfiguration: {
ideal: {
showImage: true,
},
paypal: {
amount: {
value: this.adyenPriceTotal,
currency: "USD",
},
environment: applicationConfig.ADYEN_ENVIRONMENT,
countryCode: "US", // Only needed for test. This will be automatically retrieved when you are in production.
blockPayPalVenmoButton: true,
blockPayPalPayLaterButton: true,
},
card: {
hasHolderName: true,
holderNameRequired: true,
billingAddressRequired: true,
name: "Credit or debit card",
data: {
holderName: `${this.mainStore.contactInfo.firstName} ${this.mainStore.contactInfo.lastName}`,
billingAddress: {
postalCode: this.zipCode,
country: "US"
}
}
},
},
};
const adyenPaymentType = mapIssToAdyenPaymentMethod(this.piaType);
if (adyenPaymentType) {
configuration.openPaymentMethod = {
type: adyenPaymentType,
};
}
const dropin = new Dropin(checkout, configuration);
this.dropinComponent = dropin;
dropin.mount("#adyen-container");
},
getAdyenInitRequestInfo() {
const request = {
sourceSystem: this.sourceSystem,
referralSequenceNumber: this.referralSequenceNumber,
workOrderNumber: this.workOrderNumberLastSixDigits,
zipCodeCtu: this.ctu,
amount: this.adyenPriceTotal,
city: this.city,
houseNumberOrName: this.splitStreetAddress.number,
street: this.splitStreetAddress.street,
zipCode: this.zipCode,
stateOrProvince: this.state,
returnUrl: this.payInAdvanceResponseUrl,
email: this.mainStore.contactInfo.emailAddress,
firstName: this.mainStore.contactInfo.firstName,
lastName: this.mainStore.contactInfo.lastName,
idempotencyKey: this.getIdempotencyKey(),
};
return request;
},
async resetAdyenDropin() {
this?.dropinComponent?.unmount();
await this.initializeAdyen();
},
getIdempotencyKey() {
const currentDateTime = new Date();
const currentHour = currentDateTime.getUTCHours();
const currentDate = currentDateTime.getUTCDate();
const id = this?.mainStore?.order?.referralCorrelationId;
const system = this.sourceSystem;
const total = this.adyenPriceTotal;
return `${id}-${system}-${currentDate}-${currentHour}-${total}`;
},
async handleCompletedPayment(result) {
// Fetch session info
const paymentSessionRequest = {
zipCodeCtu: this.ctu,
workOrderNumber: this.workOrderNumberLastSixDigits,
sourceSystem: this.sourceSystem,
sessionId: this.sessionId,
sessionResult: result?.sessionResult,
};
showIssLoadingModal(true);
const adyenResponse = await this.mainStore.getAdyenSessionResult(paymentSessionRequest);
const ccToken = {
subscriptionId: adyenResponse?.storedToken,
expMonth: adyenResponse?.cardExpiryMonth,
expYear: adyenResponse?.cardExpiryYear,
cardType: adyenResponse?.cardType,
billToPostalCode: this.zipCode,
billToFirstName: this.mainStore.contactInfo.firstName,
billToLastName: this.mainStore.contactInfo.lastName,
referenceNumber: adyenResponse?.workOrderNumber,
authCode: adyenResponse?.authCode,
transactionId: adyenResponse?.transactionReference,
transReferenceNumber: adyenResponse?.transactionReference,
lastFour: adyenResponse?.last4DigitsOfCard,
};
const paymentMethodFromSession = adyenResponse?.paymentMethod;
const paymentMethod = mapAdyenToIssPaymentMethod(paymentMethodFromSession);
this.mainStore.savePaymentMethodChoice(paymentMethod);
if (paymentMethod === paymentMethods.PAYPAL) {
this.mainStore.updatePaypalToken(adyenResponse?.storedToken);
} else {
this.mainStore.updateCreditCardToken(ccToken);
}
this.mainStore.updateNextGenSettledAmount(this.totalAmount);
await this.saveAndSubmitWorkOrder();
},
async handleFailedPayment(result) {
const wasPaymentCancelled = result?.resultCode === "Cancelled";
if (!wasPaymentCancelled) {
const stringToLog = `ADYEN PAYMENT NOT AUTHORIZED. Code = ${result?.resultCode}. Id = ${this.sessionId}`;
await global.$logger.logError(stringToLog);
this.hasPaymentFailureError = true;
}
this.dropinComponent?.update();
},
async handleError(error) {
const wasPaymentCancelled = error?.name === "CANCEL";
if (!wasPaymentCancelled) {
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
await global.$logger.logError(stringToLog);
this.hasPaymentFailureError = true;
}
this.dropinComponent?.update();
},
async saveAndSubmitWorkOrder() {
// Final work order submit after returning from pay in advance.
await submitWorkOrder({ submitType: submitType.SAFELITE });
this.$router.navigate(
this.navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
this.$route
);
},
showIssLoadingModal
}
};
</script>
<style lang="scss" scoped>
.iss-heritage-container-width.payment-page-container {
padding: 0 .9375rem;
#card-container {
padding-top: 1.375rem;
margin: 0;
> * {
padding: 0 .9375rem;
}
}
.order-summary-panel {
margin-bottom: 1.375rem;
}
.cart-title {
font-size: 1rem;
font-weight: 500;
margin-top: 1.25rem;
margin-bottom: .625rem;
}
.cart-dropdown-component.cart-table {
font-size: .8125rem;
}
.footer-component:deep(.footer) {
margin: 1.25rem 0 0 0;
}
.pay-in-advance-alert {
:deep(a) {
font-weight: $font-weight-bold;
}
}
}
.fade-on-route-transition {
height: unset;
}
.payment p {
margin-bottom: 0.75rem;
}
</style>

View file

@ -4,26 +4,29 @@
<div class="justify-content-center">
<siteHeader cmsWidgetName="SiteHeaderWidget" />
</div>
<div class="iss-heritage-container-width">
<div class="payment-page-container iss-heritage-content-container-width">
<alert
v-if="displayPayInAdvanceCreditCardAlert"
name="payInAdvanceCreditCardErrorAlert"
class="my-4"
cmsWidgetName="PayInAdvanceCreditCardErrorAlertWidget"
alertClass="alert-danger"
:isDismissible="false"
@textLinkClicked="paymentFailedPayLater" />
<alert
v-if="displayPayInAdvanceAfterPayAlert"
name="payInAdvanceAfterPayErrorAlert"
class="my-4"
cmsWidgetName="PayInAdvanceAfterpayErrorAlertWidget"
alertClass="alert-danger"
:isDismissible="false"
@textLinkClicked="paymentFailedPayLater" />
<div id="card-container">
<div class="iss-heritage-container-width payment-page-container">
<div id="card-container" class="row">
<div class="col-lg-8">
<alert
v-if="displayPayInAdvanceCreditCardAlert"
name="payInAdvanceCreditCardErrorAlert"
class="pay-in-advance-alert"
cmsWidgetName="PayInAdvanceCreditCardErrorAlertWidget"
alertClass="alert-danger"
:isDismissible="false"
:isCollapsible="true"
:preventDefaultOnClick="true"
@textLinkClicked="paymentFailedPayLater" />
<alert
v-if="displayPayInAdvanceAfterPayAlert"
name="payInAdvanceAfterPayErrorAlert"
class="pay-in-advance-alert"
cmsWidgetName="PayInAdvanceAfterpayErrorAlertWidget"
alertClass="alert-danger"
:isDismissible="false"
:isCollapsible="true"
:preventDefaultOnClick="true"
@textLinkClicked="paymentFailedPayLater" />
<iframe
id="card-frame"
ref="paymentFrame"
@ -35,20 +38,46 @@
allow="payment *; microphone *; geolocation *;">
</iframe>
</div>
<div
class="px-2">
<hr :class="{'my-0': isCreditCard}">
<cartDropdown
ref="cart"
:readOnly="true"
:showAsPaid="false"
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle" />
<hr class="my-0">
<div class="col-lg-4 col-md-10 col-xs-12">
<div class="order-summary-panel">
<div class="cart-title">{{ cartTitle }}</div>
<cartDropdown
ref="cart"
class="cart-dropdown-component"
:readOnly="true"
:showAsPaid="false"
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle" />
</div>
<div class="d-none d-lg-block">
<buttonMain
v-if="!afterpayModalOpen"
class="submit-payment-button"
variant="success"
:preventDefaultOnClick="true"
:buttonText="submitPaymentText"
@clickEvent="submitPaymentAction"
/>
</div>
<div
v-if="!afterpayModalOpen"
class="switch-payment-container">
<div class="switch-payment-text">{{ switchPaymentText }}</div>
<button
class="afterpay-button"
@click.prevent="switchToAfterPay">
{{ afterpayButtonText }}
<img
class="afterpay-logo"
:src="afterpayLogo"
alt="Afterpay Logo" />
</button>
</div>
</div>
</div>
<div class="col-xs-12">
<siteFooter
class="footer-component"
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="true"
@ -70,6 +99,7 @@
:target="isPaypal ? '_top' : 'card-frame'"
method="POST"
:action="checkoutUrl">
<input type="hidden" name="paymentType" :value="paymentType" />
<input
type="hidden"
name="sgSessionId"
@ -197,7 +227,7 @@
<input
type="hidden"
name="billTo_postalCode"
value="" />
:value="zipCode" />
<input
type="hidden"
name="billTo_postalCodeShow"
@ -408,6 +438,10 @@ import alert from '@/ux-components/alert/alert.vue';
import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
import { formatAmountInDollars } from '@/helpers/text-helper';
import showIssLoadingModal from '@/helpers/loading-modal-helper';
import { supportsApplePay } from '@/helpers/browser-helper';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import { submitWorkOrder } from '@/helpers/order-helper';
import submitType from '@/constants/submit-type';
export default {
name: 'payment-page',
@ -416,7 +450,8 @@ export default {
siteHeader,
siteFooter,
Form,
alert
alert,
buttonMain
},
directives: {
resize: {
@ -432,6 +467,12 @@ export default {
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
const paymentType = useMainStore().order.payment.paymentMethod;
const alertToDisplay = to.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT];
if (paymentType === paymentMethods.PAYPAL || alertToDisplay) {
useMainStore().savePaymentMethodChoice(paymentMethods.PayNow);
}
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
@ -456,7 +497,7 @@ export default {
vm.setCmsContent(resultMap.cmsContent);
vm.$nextTick(() => {
if (vm.$refs.cart) {
const cartItems = vm.$refs.cart.allCartItems;
const cartItems = vm.$refs.cart.cartItems;
vm.getPayInAdvanceLineItems(cartItems);
}
@ -493,7 +534,10 @@ export default {
totalAmount: this.getAmountDue(),
displayAmount: this.getDisplayAmountDue(),
payInAdvanceLineItems: '',
shouldBlockInteraction: false
shouldBlockInteraction: false,
afterpayModalOpen: false,
iframeLoaded: false,
iframeResizerLoaded: false
};
},
computed: {
@ -531,6 +575,21 @@ export default {
}
return '';
},
cartTitle() {
return this.getCmsContent('OrderSummaryTextWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
switchPaymentText() {
return this.getCmsContent('SwitchPaymentTextWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
},
submitPaymentText() {
return this.getCmsContent('SiteFooterWidget', widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT);
},
afterpayButtonText() {
return this.getCmsContent('AfterpayButtonWidget', widgetFields.CONTENT_GROUP_WIDGET.HEADER_TEXT);
},
afterpayLogo() {
return this.getCmsContent('AfterpayButtonWidget', widgetFields.CONTENT_GROUP_WIDGET.IMAGE);
},
isPaypal() {
return this.paymentType === paymentMethods.PAYPAL;
},
@ -540,6 +599,9 @@ export default {
isAfterPay() {
return this.paymentType === paymentMethods.AFTERPAY;
},
isPayNow() {
return this.paymentType === paymentMethods.PayNow;
},
displayPayInAdvanceCreditCardAlert() {
return this.displayPayInAdvanceAlert(paymentMethods.CREDIT_CARD);
},
@ -651,7 +713,8 @@ export default {
},
getZipCode() {
return (
useMainStore().order.serviceLocation.zipCode
useMainStore().order.customer.address.zipCode
?? useMainStore().order.serviceLocation.zipCode
?? useMainStore().order.serviceLocation.provider.address.zipCode
);
},
@ -703,25 +766,38 @@ export default {
if (iframe) {
iframe.onload = () => {
if (iframe.contentWindow) {
iframe.contentWindow.postMessage('hybrid', '*');
if (supportsApplePay()) {
iframe.contentWindow.postMessage("hybrid-with-applepay", "*");
} else {
iframe.contentWindow.postMessage('hybrid', '*');
}
if (this.isAfterPay) {
this.afterpayModalOpen = true;
iframe.contentWindow.postMessage('afterpay', '*');
} else {
iframe.contentWindow.postMessage('CreditCardChosen', '*');
}
this.iframeLoaded = true;
}
};
}
});
},
handleIFrameContentWindowMessage(event) {
const iframe = this.$refs.paymentFrame;
if (typeof event.data !== 'string') {
return;
}
if (event.data.includes('afterpayClosed')) {
this.backButtonAction();
this.allowIFrameResize(iframe);
if (this.isAfterPay) {
this.backButtonAction();
} else {
iframe.contentWindow.postMessage('CreditCardChosen', '*');
this.afterpayModalOpen = false;
}
}
if (useMainStore().order.payment.paymentMethod !== paymentMethods.PayNow) {
@ -738,7 +814,6 @@ export default {
useMainStore().savePaymentMethodChoice(paymentMethods.AFTERPAY);
}
if (event.data.includes('applepayOpened')) {
this.showIssLoadingModal(true);
useMainStore().savePaymentMethodChoice(paymentMethods.APPLEPAY);
}
if (event.data.includes('showLoaderForPaypal')) {
@ -762,7 +837,10 @@ export default {
this.$route.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT] === payMethod
);
},
paymentFailedPayLater() {
async paymentFailedPayLater() {
this.showIssLoadingModal(true);
this.mainStore.savePaymentMethodChoice(paymentMethods.PAY_AT_TIME_OF_SERVICE);
await submitWorkOrder({ submitType: submitType.SAFELITE });
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
@ -778,17 +856,108 @@ export default {
this.$route
);
},
submitPaymentAction() {
const iframe = this.$refs.paymentFrame;
if (iframe && iframe.contentWindow && this.iframeLoaded) {
iframe.contentWindow.postMessage('submitPayment', '*');
}
},
switchToAfterPay() {
const iframe = this.$refs.paymentFrame;
if (iframe && iframe.contentWindow && this.iframeLoaded) {
this.afterpayModalOpen = true;
this.lockIFrameSize(iframe);
iframe.contentWindow.postMessage('afterpay-switch', '*');
}
},
lockIFrameSize(iframe) {
if (iframe && iframe.iFrameResizer) {
iframe.iFrameResizer.removeListeners();
}
},
allowIFrameResize(iframe) {
if (iframe) {
iframeResize({ checkOrigin: false }, iframe);
}
},
showIssLoadingModal
}
};
</script>
<style lang="scss" scoped>
.iss-heritage-container-width {
.payment-page-container {
position: relative;
min-height: 1px;
padding-left: .9375rem;
padding-right: .9375rem;
.iss-heritage-container-width.payment-page-container {
padding: 0 .9375rem;
#card-container {
padding-top: 1.375rem;
margin: 0;
> * {
padding: 0 .9375rem;
}
}
.order-summary-panel {
margin-bottom: 1.375rem;
}
.cart-title {
font-size: 1rem;
font-weight: 500;
margin-top: 1.25rem;
margin-bottom: .625rem;
}
.cart-dropdown-component.cart-table {
font-size: .8125rem;
}
.submit-payment-button {
width: 100%;
font-weight: $font-weight-normal;
}
.switch-payment-container {
@include media-breakpoint-up(lg) {
padding-top: 1.25rem;
}
}
.switch-payment-text {
padding-top: 1rem;
padding-bottom: .625rem;
border-top: 1px solid #f0f1f2;
}
.footer-component:deep(.footer) {
margin: 1.25rem 0 0 0;
}
.afterpay-button {
width: 100%;
height: 2.875rem;
border: solid 1px #cacbcc;
border-radius: 1.375rem;
background-color: white;
box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2);
color: $darker-gray;
font-weight: 400;
margin-bottom: 1.25rem;
padding: .5rem 1rem;
&:focus {
background: $background-color-selected;
}
&:active {
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
}
.pay-in-advance-alert {
width: 80%;
@media (max-width: 35rem) {
width: 100%;
}
}
}

View file

@ -0,0 +1,160 @@
<template>
<Form
ref="theForm"
@submit="onSubmit"
@invalidSubmit="onInvalidSubmit">
</Form>
</template>
<script>
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store/index.js';
import queryStrings from '@/constants/query-strings';
import { getCartTotal } from '@/helpers/cart-helper';
import { submitWorkOrder } from '@/helpers/order-helper.js';
import { createAdyenCheckout, generateCcToken, getSessionInfo, mapAdyenToIssPaymentMethod } from '@/helpers/adyen-helper';
import navigationScenarios from '@/router/router-constants/navigation-scenarios';
import submitType from '@/constants/submit-type';
import showIssLoadingModal from '@/helpers/loading-modal-helper';
import { paymentMethods } from '@/constants/payment-method-constants';
export default {
name: 'payment-return-adyen',
components: {
Form
},
mixins: [BaseFormMixin],
async mounted() {
showIssLoadingModal(true);
const store = useMainStore();
const sessionId = this.$route?.query?.sessionId;
const redirectResult = this.$route?.query?.redirectResult;
if (sessionId && redirectResult) {
let result = null;
try {
result = await this.finalizeAdyenPayment(sessionId, redirectResult);
} catch (unexpectedResult) {
// If user cancelled payment, handle without error message
if (unexpectedResult.code === "Cancelled" || unexpectedResult.code === "CANCEL") {
this.$router.navigate(navigationScenarios.PAY_IN_ADVANCE_CANCEL, this.$route);
return;
}
// Otherwise, failure scenario.
// Payment fails, so return user to payment-page-adyen screen to try again or pay later.
this.$router.navigate(
navigationScenarios.PAY_IN_ADVANCE_ERROR,
this.$route,
{
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: true
}
);
return;
}
const sessionInfo = await getSessionInfo(sessionId, result.sessionResult);
const paymentMethod = mapAdyenToIssPaymentMethod(sessionInfo?.paymentMethod);
const amountDue = getCartTotal(store.order);
const ccToken = generateCcToken(sessionInfo);
if (paymentMethod === paymentMethods.AFTERPAY) {
ccToken.authCode = "831001";
ccToken.cardType = "VS";
ccToken.lastFour = "1111";
ccToken.expMonth = "03";
ccToken.expYear = "2030";
}
store.savePaymentMethodChoice(paymentMethod);
store.updateCreditCardToken(ccToken);
store.updateNextGenSettledAmount(amountDue);
store.clearSaveSessionPromise();
try {
await this.saveAndSubmitWorkOrder();
return;
} catch (error) {
this.$router.navigate(
navigationScenarios.PAY_IN_ADVANCE_ERROR,
this.$route,
{
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: true
}
);
}
}
this.$router.navigate(
navigationScenarios.PAY_IN_ADVANCE_ERROR,
this.$route,
{
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: true
}
);
},
methods: {
finalizeAdyenPayment(sessionId, redirectResult) {
return new Promise((resolve, reject) => {
createAdyenCheckout({
session: {
id: sessionId,
},
handlers: {
onPaymentCompleted: (result, component) => {
resolve(result);
},
onPaymentFailed: (result, component) => {
if (result?.resultCode !== "Cancelled") {
const stringToLog = `ADYEN PAYMENT NOT AUTHORIZED. Code = ${result?.resultCode}. Id = ${sessionId}`;
global.$logger.logError(stringToLog);
}
reject({
code: result?.resultCode,
message: "Payment failed from Adyen",
});
},
onError: (error, component) => {
if (error?.name !== "CANCEL") {
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
global.$logger.logError(stringToLog);
}
reject({
code: error?.name,
message: "Error from Adyen",
});
},
},
options: {},
}).then(
(checkout) => {
checkout.submitDetails({
details: {
redirectResult: redirectResult,
},
});
},
(error) => {
reject(error);
}
);
});
},
async saveAndSubmitWorkOrder() {
// Final work order submit after returning from pay in advance.
await submitWorkOrder({ submitType: submitType.SAFELITE });
this.$router.navigate(
this.navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
this.$route
);
}
}
};
</script>

View file

@ -10,8 +10,6 @@ import { getRandomString, getRandomInt } from '@/helpers/data-generation';
import endorsementOptions from '@/constants/endorsement-options';
import { createTestingPinia } from '@pinia/testing';
import vehicleSelectionOptions from '@/constants/vehicle-selection-options';
import bailoutCode from '@/constants/bailoutCode';
import bailoutMessage from '@/constants/bailoutMessage';
import issPageValues from '@/router/router-constants/issPage-values';
// Mock fetchCmsContentForPage
@ -720,54 +718,4 @@ describe('policy-vehicles.vue', () => {
// Assert
expect(wrapper.vm.selectedVehicleVin).toBe(vin);
});
test('selected vehicle is a non-serviceable big truck, display no service alert', async () => {
// Arrange
const vin = getRandomString(17, 17);
const policyVehicle = {
vin,
isBigTruck: true,
canSafeliteService: false
};
const { wrapper } = setupMocks({});
await wrapper.setData({
selectedVehicleVin: vin,
selectedPolicyVehicle: policyVehicle,
policyVehicles: [policyVehicle],
policyVinFound: true
});
wrapper.vm.displayNoServiceAlert = true;
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.findComponent({ ref: 'AlertNoService' }).isVisible()).toBe(true);
});
test('selected vehicle is a serviceable big truck, do not display no service alert', async () => {
// Arrange
const vin = getRandomString(17, 17);
const policyVehicle = {
vin,
isBigTruck: true,
canSafeliteService: true
};
const { wrapper } = setupMocks({});
await wrapper.setData({
selectedVehicleVin: vin,
selectedPolicyVehicle: policyVehicle,
policyVehicles: [policyVehicle],
policyVinFound: true
});
wrapper.vm.displayNoServiceAlert = false;
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.findComponent({ ref: 'AlertNoService' }).exists()).toBe(false);
});
});

View file

@ -27,17 +27,10 @@
buttonText="Add another vehicle"
class="w-100"
@clickEvent="addAnotherVehicle" />
<alert
v-if="displayNoServiceAlert"
ref="AlertNoService"
class="mt-3"
cmsWidgetName="AlertVehicleSelection"
alertClass="alert-danger"
:isDismissable="false" />
<siteFooter
ref="siteFooter"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid || displayNoServiceAlert"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="backButtonAction" />
</div>
@ -67,10 +60,10 @@ import {
} from '@/helpers/policy-vehicle-helper';
import coverageType from '@/constants/coverage-type';
import coverageStatuses from '@/constants/coverage-statuses';
import alert from '@/ux-components/alert/alert.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import buttonMain from '@/ux-components/button-main/button-main.vue';
import { buttonVariants } from '@/constants/component-variants';
import bailoutMessage from '@/constants/bailoutMessage';
export default {
name: 'policy-vehicles',
@ -80,8 +73,7 @@ export default {
siteHeader,
siteFooter,
policyVehiclesQuestion,
Form,
alert
Form
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -103,7 +95,6 @@ export default {
rules: {
optionRequired: globalRules.OPTION_REQUIRED
},
displayNoServiceAlert: false,
buttonVariants
};
},
@ -147,7 +138,6 @@ export default {
},
watch: {
async selectedVehicleVin(value) {
this.displayNoServiceAlert = false;
if (value === vehicleSelectionOptions.VEHICLE_NOT_LISTED) {
// clear previously selected vehicle and image
this.mainStore.resetVehicleState();
@ -157,10 +147,6 @@ export default {
// get vehicle details from selected VIN
try {
const vehicle = await this.lookupVehicleByVin(value);
if (!vehicle?.data.canSafeliteService) {
this.displayNoServiceAlert = true;
return;
}
if (vehicle) {
// save selected vehicle to the store
useMainStore().updateVehicle({
@ -201,6 +187,11 @@ export default {
try {
const vehicleLookupResponse = await this.lookupVehicleByVin(this.selectedVehicleVin);
if (!vehicleLookupResponse?.data.canSafeliteService) {
this.mainStore.setBailout(
bailoutMessage.HeavyTruckVehicle(vehicleLookupResponse?.data?.carId)
);
}
useMainStore().updateVehicle({
...vehicleLookupResponse.data,
policyVehicleId: vehicle.id,
@ -242,6 +233,11 @@ export default {
{},
{}
);
} else if (this.mainStore.isBailout) {
this.$router.navigate(
this.navigationScenarios.BAILOUT,
this.$route
);
} else if (this.endorsementsForSelectedVehicle?.length > 0
&& (this.endorsementsForSelectedVehicle?.includes(endorsementOptions.PARKING_GUARD)
|| this.endorsementsForSelectedVehicle?.includes(endorsementOptions.EDUCATOR))

View file

@ -2,7 +2,7 @@ import ProviderPreference from '@/layouts/provider-preference/provider-preferenc
import { shallowMount } from '@vue/test-utils';
import settleAllPromises from '@/helpers/layout-helper.js';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import { fetchCmsContentForPage, processIfStatements } from '@/helpers/cms-content-helper';
import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store';
import navigationScenarios from '@/router/router-constants/navigation-scenarios';
@ -13,7 +13,8 @@ jest.mock('@/helpers/layout-helper.js', () => jest.fn());
// Mock fetchCmsContentForPage
jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn(),
setupModalLinks: jest.fn()
setupModalLinks: jest.fn(),
processIfStatements: jest.fn()
}));
/** @ignore */

View file

@ -58,11 +58,11 @@
// Import Supporting Files
import {
fetchCmsContentForPage,
processIfStatements,
setupModalLinks
} from '@/helpers/cms-content-helper';
import settleAllPromises from '@/helpers/layout-helper';
import errorMessages from '@/constants/error-messages';
import buttonQuestion from '@/digital-components/button-question/button-question.vue';
import showIssLoadingModal from '@/helpers/loading-modal-helper';
import bailoutMessage from '@/constants/bailoutMessage';
import baseFormMixin from '@/mixins/base-form-mixin';
@ -133,26 +133,14 @@ export default {
return this.getCmsContent('ScheduleWithSafeliteText', 'BodyText');
},
scheduleWithOtherText() {
return this.getCmsContent('ScheduleWithOtherText', 'BodyText');
const text = this.getCmsContent('ScheduleWithOtherText', 'BodyText');
return processIfStatements(text, 'custom', this.getStateSpecificText);
}
},
mounted() {
setupModalLinks(this);
},
methods: {
getHeaderTextFromCms(cmsWidgetName) {
const headerText = this.getCmsContent(cmsWidgetName, 'HeaderText');
return headerText?.replace(
'{custom:SafeliteLogo}',
"<span class='safeliteLogo'></span>"
);
},
getSubheaderTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, 'SubheaderText');
},
getBodyTextFromCms(cmsWidgetName) {
return this.getCmsContent(cmsWidgetName, 'BodyText');
},
arePagePrerequisiteValid() {
return true;
},
@ -191,6 +179,9 @@ export default {
},
openStateSteeringModal() {
this.$refs[STEERING_MODAL_REF_NAME].openModal();
},
getStateSpecificText(value) {
return this.mainStore.order.customer.address.state?.toLowerCase() === value?.toLowerCase();
}
}
};

View file

@ -1,21 +1,24 @@
<template>
<modal
:ref="ModalName"
:modalId="ModalName"
:footerButtonText="ModalCloseButtonText"
@footerButtonEvent="footerButtonClick">
<h5 class="text-center mb-4">
{{ ModalHeadline }}
</h5>
<div>
<div
class="mb-4"
v-html="ModalBodyText"></div>
<div
v-if="ModalSubBodyText"
v-html="ModalSubBodyText"></div>
</div>
</modal>
<div class="steering-modal-container">
<modal
:ref="ModalName"
:modalId="ModalName"
:footerButtonText="ModalCloseButtonText"
@footerButtonEvent="footerButtonClick">
<h5 class="text-center mb-4 headline-text">
{{ ModalHeadline }}
</h5>
<div>
<div
class="mb-4"
v-html="ModalBodyText"></div>
<div
class="mb-4"
v-if="ModalSubBodyText"
v-html="ModalSubBodyText"></div>
</div>
</modal>
</div>
</template>
<script>
@ -74,24 +77,13 @@ export default {
</script>
<style lang="scss" scoped>
.modal {
&.modal-component {
.modal-dialog {
.modal-content {
.modal-body {
.modal-sub-body {
color: $gray-600;
}
ul {
margin-bottom: 0;
}
p {
&:last-child {
margin-bottom: 0;
}
}
}
}
:deep(.modal) {
.modal-footer {
justify-content: center;
button {
background-color: $heritage-blue-secondary;
color: $white;
width: 71%;
}
}
}

View file

@ -1,53 +1,58 @@
<template>
<modal
:ref="ModalName"
:modalId="ModalName"
:footerButtonText="ModalCloseButtonText"
:isButtonDisabled="isButtonDisabled"
@footerButtonEvent="footerButtonClick">
<div class="form-test-invalid">
<h5 class="text-center mb-4">
{{ ModalHeadline }}
</h5>
<div>
<div
class="mb-4"
v-html="ModalBodyText"></div>
<tpaRecalToggle
class="mb-5"
cmsWidgetName="TPARecalModalToggle" />
<buttonQuestion
ref="tpaRecalQuestion"
v-model="tpaRecalAnswer"
class="radioQuestion safelite-or-tpa-question windshield-chip-count-question mb-4"
:cmsWidgetName="buttonCmsWidgetName"
:questionText="tpaRecalQuestionText"
:answers="tpaRecalAnswersFromCms"
groupName="tpaRecalQuestion"
buttonID="tpaRecalQuestion"
buttonTypeString="listButtonHorizontal"
isRequired></buttonQuestion>
<checkBox
v-if="showAcknowledgementCheckbox"
ref="tpaAcknowledgement"
v-model="acknowledged"
class="mb-2"
:class="showAcknowledgementError && ' has-error'"
:validationRules="rules.optionRequired"
checkboxName="tpaAcknowledgement"
buttonID="tpaAcknowledgement"
:tabIndex="0"
:checkboxLabel="ModalSubBodyText"
:screenReaderOnlyText="ModalSubBodyText"
isRequired />
<div
v-if="showAcknowledgementError || showNoSelectionError"
class="row form-test-error mt-1">
<p>{{ errorMessage }}</p>
<div class="tpa-recal-modal-container">
<modal
:ref="ModalName"
:modalId="ModalName"
:footerButtonText="ModalCloseButtonText"
:isButtonDisabled="isButtonDisabled"
@footerButtonEvent="footerButtonClick"
:displayCloseButton="false"
:displayCloseLink="true">
<div class="form-test-invalid">
<h5>
{{ ModalHeadline }}
</h5>
<div>
<div
class="mb-4"
v-html="ModalBodyText"></div>
<tpaRecalToggle
class="mb-5"
cmsWidgetName="TPARecalModalToggle" />
<buttonQuestion
ref="tpaRecalQuestion"
v-model="tpaRecalAnswer"
class="tpa-recal-question"
:cmsWidgetName="buttonCmsWidgetName"
:questionText="tpaRecalQuestionText"
:answers="tpaRecalAnswersFromCms"
groupName="tpaRecalQuestion"
buttonID="tpaRecalQuestion"
buttonTypeString="listButtonHorizontal"
isRequired>
</buttonQuestion>
<checkBox
v-if="showAcknowledgementCheckbox"
ref="tpaAcknowledgement"
v-model="acknowledged"
class="mb-2 mt-4"
:class="showAcknowledgementError && ' has-error'"
:validationRules="rules.optionRequired"
checkboxName="tpaAcknowledgement"
buttonID="tpaAcknowledgement"
:tabIndex="0"
:checkboxLabel="ModalSubBodyText"
:screenReaderOnlyText="ModalSubBodyText"
isRequired />
<div
v-if="showAcknowledgementError || showNoSelectionError"
class="row form-test-error mt-1">
<p>{{ errorMessage }}</p>
</div>
</div>
</div>
</div>
</modal>
</modal>
</div>
</template>
<script>
@ -152,39 +157,51 @@ export default {
</script>
<style lang="scss" scoped>
.modal {
&.modal-component {
.modal-dialog {
.modal-content {
.modal-body {
.modal-sub-body {
color: $gray-600;
}
ul {
margin-bottom: 0;
}
p {
&:last-child {
margin-bottom: 0;
}
margin-bottom: .5rem;
}
}
:deep(.modal) {
h5 {
font-size: 1.625rem;
margin-bottom: .625rem;
}
.modal-body {
color: #4d4e53;
}
.modal-footer {
padding-top: 22px;
button {
background-color: $green;
color: $white;
font-weight: $font-weight-bold;
&:hover {
background-color: $green;
}
}
}
.safelite-or-tpa-question {
:deep(.question-text) {
span {
.tpa-recal-question > div:first-of-type {
margin-bottom: 20px;
}
.button-question {
fieldset {
box-shadow: 0px 2px 5px -5px rgba(0, 0, 0, 0.25);
}
}
.list-button-horizontal {
&.selected {
input {
background-color: $background-color-selected;
}
}
}
.form-check {
p {
font-size: 1rem;
}
.form-check-input:checked {
+ label p {
font-size: 1rem;
color: #4d4e53;
font-weight: 500;
}
}
}
}
.form-test-error {
p {
font-weight: 500;
}
}
</style>

View file

@ -20,6 +20,7 @@
:schedulingInshopAvailabilityRating="inShopAvailabilityRating"
@appointmentTypeChanged="appointmentTypeChangedFromServiceLocation"
@cityUpdated="cityUpdatedFromServiceLocation"
@clearMobileData="clearMobileDataFromServiceLocation"
@inShopZipUpdated="inShopZipUpdatedFromServiceLocation"
@mobileZipUpdated="mobileZipUpdatedFromServiceLocation"
@providerChanged="providerChangedFromServiceLocation" />
@ -89,9 +90,8 @@ import {
import settleAllPromises from '@/helpers/layout-helper';
import showIssLoadingModal from '@/helpers/loading-modal-helper.js';
import {
getProviderData,
getZipCodeData,
onProviderChanged
getPricedMobileFeePart,
getZipCodeData
} from '@/helpers/service-location-helper';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
@ -225,11 +225,24 @@ export default {
async beforeRouteEnter(to, from, next) {
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
const { isMobileAppointment, storeServiceLocation, storeSelectedProvider, serviceZipCode } = getProviderData();
const zipCodeData = getZipCodeData(serviceZipCode);
const serviceabilityDetailsPromise = useMainStore().getServiceabilityDetails(serviceZipCode);
const providersPromise = useMainStore().getSafeliteProviders(serviceZipCode, 150);
let storeServiceLocation = useMainStore().order.serviceLocation;
let storeSelectedProvider = storeServiceLocation?.provider;
const storeSelectedSchedule = useMainStore().order.schedule;
if (storeSelectedProvider?.providerNumber && !storeSelectedSchedule?.routeCode) {
useMainStore().resetServiceLocationProvider();
storeServiceLocation = useMainStore().order.serviceLocation;
storeSelectedProvider = storeServiceLocation?.provider;
}
const storeSelectedAppointmentType = storeServiceLocation?.appointmentType;
const isMobileAppointment = storeSelectedAppointmentType === AppointmentTypeStrings.MOBILE
|| storeSelectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
const serviceZipCode = storeServiceLocation?.zipCode || useMainStore().order.customer.address.zipCode;
const zipCodeData = getZipCodeData(serviceZipCode);
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode);
const serviceabilityDetailsPromise = useMainStore().getServiceabilityDetails(serviceZipCode);
const getGlassFeesPromise = useMainStore().getGlassFees();
const providersPromise = useMainStore().getSafeliteProviders(serviceZipCode, 150);
const premiumFeePromise = useMainStore().getMobilePremiumFee();
const premiumFeeWithPricePromise = premiumFeePromise.then((result) => {
if (result.data) {
@ -244,6 +257,14 @@ export default {
resultKey: 'cmsContent',
promise: cmsContentPromise
},
{
resultKey: 'mobileFeePart',
promise: mobileFeePartPromise
},
{
resultKey: 'glassFees',
promise: getGlassFeesPromise
},
{
resultKey: 'serviceabilityDetails',
promise: serviceabilityDetailsPromise
@ -256,10 +277,6 @@ export default {
resultKey: 'providers',
promise: providersPromise
},
{
resultKey: 'providerChanged',
promise: onProviderChanged()
},
{
resultKey: 'premiumFeeWithPrice',
promise: premiumFeeWithPricePromise
@ -268,7 +285,7 @@ export default {
// use resultMap to populate layout content.
const resultMap = await settleAllPromises(promiseResultMap);
const providersUnderOneHundredMiles = resultMap.providers?.shopProviders?.filter((provider) => provider.distanceMiles <= 100);
const providersUnderOneHundredMiles = resultMap.providers?.shopProviders?.filter((provider) => provider.distanceInMiles <= 100);
const serviceLocationData = {
defaultMobileZipCode: isMobileAppointment
? storeServiceLocation?.zipCode
@ -285,9 +302,9 @@ export default {
useMainStore().updateIsSafeliteProvider(true);
next(async (vm) => {
const foundProviderInFullList = resultMap.providers?.shopProviders
.find((provider) => provider.providerNumber === storeSelectedProvider?.providerNumber) || resultMap.providers?.shopProviders[0];
.find((provider) => provider.providerNumber === storeSelectedProvider?.providerNumber) || null;
const foundProviderInHundredMiles = providersUnderOneHundredMiles
.find((provider) => provider.providerNumber === storeSelectedProvider?.providerNumber) || providersUnderOneHundredMiles[0];
.find((provider) => provider.providerNumber === storeSelectedProvider?.providerNumber) || null;
if (!foundProviderInHundredMiles && !foundProviderInFullList) {
if (storeSelectedProvider?.providerNumber && resultMap.providers?.shopProviders) {
@ -296,11 +313,12 @@ export default {
} else if (!foundProviderInHundredMiles && foundProviderInFullList) {
serviceLocationData.providers?.shopProviders.push(foundProviderInFullList);
}
const initialServiceLocationObj = {
mobileProviderNumber: isMobileAppointment
? storeSelectedProvider?.providerNumber
: null,
provider: isMobileAppointment ? null : foundProviderInFullList,
provider: isMobileAppointment ? null : foundProviderInFullList || resultMap.providers?.shopProviders[0] || null,
zipCode: useMainStore().order.serviceLocation.zipCode || useMainStore().order.customer.address.zipCode,
zipCodeCtu: resultMap.zipCodeData?.zipCodeCtu
};
@ -319,8 +337,10 @@ export default {
return {
inShopAvailabilityRating: 'high',
inShopDatesData: [],
isDatePickerRefreshing: false,
isMobileView: false,
mobileDatesData: [],
mobileFeePart: null,
mobilePremiumAppointmentFee: null,
mobileProviderNumber: null,
selectableDatesData: [],
@ -341,14 +361,18 @@ export default {
return false;
}
const validMobileAppointmentType = ((this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)
&& serviceLocationToUse.mobileProviderNumber);
const validInShopAppointmentType = ((this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP
|| this.selectedAppointmentType === AppointmentTypeStrings.DROP_OFF)
&& (this.selectedProvider?.providerNumber || serviceLocationToUse.provider?.providerNumber));
const serviceLocationPreReqs = serviceLocationToUse.zipCode !== null
&& serviceLocationToUse.zipCodeCtu !== null
&& serviceLocationToUse.appointmentType !== null
&& (((this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP)
&& serviceLocationToUse.mobileProviderNumber)
|| ((this.selectedAppointmentType === AppointmentTypeStrings.IN_SHOP || this.selectedAppointmentType === AppointmentTypeStrings.DROP_OFF)
&& (this.selectedProvider?.providerNumber || serviceLocationToUse.provider?.providerNumber)));
&& (validMobileAppointmentType || validInShopAppointmentType)
&& !this.isDatePickerRefreshing;
const damageInfo = useMainStore().order.damage.isRepair
|| (useMainStore().order.lineItems?.glassParts != null && useMainStore().order.lineItems.glassParts.length > 0);
@ -453,6 +477,14 @@ export default {
cityUpdatedFromServiceLocation(newCity) {
this.selectedServiceLocationCity = newCity;
},
clearMobileDataFromServiceLocation() {
if (this.selectedServiceLocation) {
this.selectedServiceLocation.mobileProviderNumber = null;
}
this.mobileDatesData = [];
this.mobileProviderNumber = null;
this.selectedMobileZipCode = null;
},
dateSelectedFromPicker(date) {
this.selectedDate = date;
this.showDatePickerError = false;
@ -531,7 +563,6 @@ export default {
daysFromStart: endDateObject.daysFromStart || null
}))
.catch(() => {
console.warn('Error fetching initial available dates...');
return {
todayDate: todayDateString,
initialViewStartDate,
@ -647,9 +678,7 @@ export default {
}
},
mobileZipUpdatedFromServiceLocation(mobileZipServiceLocation) {
if (mobileZipServiceLocation
&& (this.mobileProviderNumber !== mobileZipServiceLocation.mobileProviderNumber
|| !this.hasNeededServiceLocationData)) {
if (mobileZipServiceLocation) {
this.mobileProviderNumber = mobileZipServiceLocation.mobileProviderNumber;
this.selectedMobileZipCode = mobileZipServiceLocation.zipCode;
this.selectedServiceLocation = mobileZipServiceLocation;
@ -661,22 +690,27 @@ export default {
},
async providerChangedFromServiceLocation(newProvider) {
this.selectedProvider = newProvider?.provider;
if (newProvider?.provider) {
this.mainStore.updateServiceLocation(newProvider);
if (newProvider?.refreshDatePicker && newProvider.provider) {
this.mainStore.updateServiceLocationProvider(newProvider.provider);
showIssLoadingModal(true);
await onProviderChanged();
if (newProvider.refreshDatePicker) {
await this.refreshDatePicker();
} else {
showIssLoadingModal(false);
}
await this.mainStore.getBillToInfo(newProvider.provider?.providerNumber)
.then(() => {
this.refreshDatePicker();
})
.catch(() => {
console.warn('Error fetching bill to info...');
showIssLoadingModal(false);
});
}
},
async refreshDatePicker() {
this.resetLocalFlags();
this.isDatePickerRefreshing = true;
await this.getDatePickerInitialData().then((data) => {
this.selectableDatesData = data.initialShopTimeSlotsResponse;
this.$refs.datePicker.initializeComponent(data);
this.isDatePickerRefreshing = false;
showIssLoadingModal(false);
});
},
@ -700,7 +734,7 @@ export default {
appointmentTypeToUse = AppointmentTypeStrings.DROP_OFF;
}
this.mainStore.updateAppointmentType(appointmentTypeToUse);
await this.$refs.serviceLocation.forwardButtonAction(appointmentTypeToUse);
this.mainStore.saveSchedule(this.selectedTimeSlotInfo.timeSlot);
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,

View file

@ -124,7 +124,7 @@ const mountOptions = {
alert: true,
textBlock: true,
appointmentTypeQuestion: true,
contentGroupModal: true,
recalModal: true,
siteFooter: true,
siteHeader: true,
siteSubHeader: true,
@ -203,7 +203,7 @@ describe('service-location.vue', () => {
const newMobileServiceZipCode = '45433';
// Act
mobileServiceZipCodeQuestion.vm.$emit('update:modelValue', newMobileServiceZipCode);
mobileServiceZipCodeQuestion.vm.$emit('service-zip-code-updated', newMobileServiceZipCode);
await wrapper.vm.$nextTick();
// Assert

View file

@ -1,11 +1,5 @@
<template>
<div class="service-location">
<alert
v-if="displayRecalibrationWarning"
ref="alertRecalNoMobile"
cmsWidgetName="AlertRecalNoMobileWidget"
alertClass="alert-warning"
@textLinkClicked="openModalAction" />
<alert
v-if="displayBigTruckNoShops"
ref="alertBigTruckNoShops"
@ -19,6 +13,12 @@
<span
v-if="!requiresInshopRecalibration">{{ appointmentQuestionText }}</span>
</div>
<alert
v-if="displayRecalibrationWarning"
ref="alertRecalNoMobile"
cmsWidgetName="AlertRecalNoMobileWidget"
alertClass="alert-warning"
@textLinkClicked="openModalAction" />
<alert
v-if="displayServiceableInshopOnly"
ref="alertInshopOnly"
@ -96,13 +96,14 @@
</div>
<serviceZipQuestion
ref="mobileServiceZipCodeQuestion"
v-model="mobileZipCode"
customInputId="mobileServiceZipCode"
class="mobile-service-zip-question"
:parentZipCode="mobileZipCode"
:placeholderText="mobileZipPlaceholder"
:serviceZipFormatErrorMessage="errorMessages.MOBILE_SERVICE_ZIP_FORMAT"
:hasError="mobileZipError !== ''"
cmsWidgetName="MobileZipWidget" />
cmsWidgetName="MobileZipWidget"
@serviceZipCodeUpdated="handleServiceZipCodeChanged" />
<div
v-if="mobileZipError"
ref="errorMessageDiv"
@ -116,9 +117,8 @@
cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="disclaimer" />
</div>
<contentGroupModal
<recalModal
:ref="RECAL_MODAL_REF_NAME"
cssModalHeadlineClass="text-center"
cmsWidgetName="RecalModal" />
</div>
</template>
@ -139,10 +139,10 @@ import alert from '@/ux-components/alert/alert.vue';
import textBlock from '@/digital-components/text-block/text-block.vue';
import appointmentTypeQuestion from '@/layouts/schedule-page/service-location/appointment-type-question/appointment-type-question.vue';
import baseFormMixin from '@/mixins/base-form-mixin';
import contentGroupModal from '@/iss-components/content-group-modal/content-group-modal.vue';
import shopAddress from '@/layouts/schedule-page/service-location/shop-address/shop-address.vue';
import serviceZipQuestion from '@/layouts/schedule-page/service-location/service-zip-question/service-zip-question.vue';
import widgetFields from '@/constants/cms-widget-fields';
import recalModal from '@/iss-components/recal-modal/recal-modal.vue';
const RECAL_MODAL_REF_NAME = 'RecalModal';
@ -152,12 +152,12 @@ export default {
alert,
textBlock,
appointmentTypeQuestion,
contentGroupModal,
shopAddress,
serviceZipQuestion
serviceZipQuestion,
recalModal
},
mixins: [baseFormMixin],
emits: ['appointment-type-changed', 'city-updated', 'in-shop-zip-updated', 'mobile-zip-updated', 'provider-changed'],
emits: ['appointment-type-changed', 'city-updated', 'clear-mobile-data', 'in-shop-zip-updated', 'mobile-zip-updated', 'provider-changed'],
props: {
schedulingInshopAvailabilityRating: {
type: String,
@ -182,6 +182,7 @@ export default {
isRecalibrationServiceableMobile: null,
selectedAppointmentType: this.getSelectedAppointmentType(),
selectedProvider: this.getSelectedProvider(),
mobileFeePart: null,
mobileProviderNumber: null,
zipContainsMilitaryBase: false,
zipCodeCtu: null,
@ -236,7 +237,8 @@ export default {
);
},
isMobile() {
return this.selectedAppointmentType === AppointmentTypeStrings.MOBILE;
return this.selectedAppointmentType === AppointmentTypeStrings.MOBILE
|| this.selectedAppointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
},
isServiceableInshop() {
if (this.isRecalibrationServiceableInshop !== null) {
@ -298,16 +300,7 @@ export default {
},
async mobileZipCode(newZip, oldZip) {
if (newZip !== '' && newZip !== oldZip && !this.initializingData) {
showIssLoadingModal(true);
await this.updateMobileZip();
const updateMobileZipServiceLocationObj = {
mobileProviderNumber: this.mobileProviderNumber,
provider: null,
refreshDatePicker: true,
zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu
};
this.$emit('mobile-zip-updated', updateMobileZipServiceLocationObj);
await this.processMobileZipCodeChange();
}
},
schedulingInshopAvailabilityRating(newRating) {
@ -339,27 +332,16 @@ export default {
}
},
selectedProvider(newProvider, oldProvider) {
if (newProvider?.providerNumber !== oldProvider?.providerNumber) {
if (newProvider?.providerNumber !== oldProvider?.providerNumber || this.availabilityRating === null) {
const appointmentIsInshop = this.isInshop;
const returnedProvider = {
refreshDatePicker: appointmentIsInshop && oldProvider?.providerNumber !== null,
state: this.state,
zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu,
appointmentType: this.selectedAppointmentType,
provider: !this.isMobile ? newProvider : {
providerNumber: this.mobileProviderNumber,
address: {
streetAddress: null,
city: null,
state: null,
zipCode: null,
zipCodeCtu: null
}
}
provider: newProvider,
refreshDatePicker: appointmentIsInshop && oldProvider?.providerNumber !== null
};
this.$emit('provider-changed', returnedProvider);
if (newProvider?.providerNumber !== oldProvider?.providerNumber) {
this.$emit('provider-changed', returnedProvider);
}
}
}
},
@ -369,6 +351,34 @@ export default {
await this.setData(initialData);
this.initializingData = false;
},
async forwardButtonAction(appointmentType) {
let provider = this.selectedProvider;
this.mainStore.updateMobileFee(null);
if (this.isMobile) {
if (this.mainStore.isNoComp || this.mainStore.isITAC) {
this.mainStore.updateMobileFee(this.mobileFeePart);
}
provider = {
providerNumber: this.mobileProviderNumber,
address: {
streetAddress: null,
city: null,
state: null,
zipCode: null,
zipCodeCtu: null
}
};
}
this.mainStore.updateServiceLocation({
state: this.state,
zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu,
appointmentType: appointmentType || this.selectedAppointmentType,
provider
});
},
openModalAction(modalName) {
this.$refs[modalName].openModal();
},
@ -427,6 +437,32 @@ export default {
this.$emit('in-shop-zip-updated', inShopZipServiceLocationObj);
});
},
async handleServiceZipCodeChanged(serviceZipCodeFromQuestion) {
if (serviceZipCodeFromQuestion !== this.mobileZipCode) {
// Can probs just make this one call but have to think about initialization flow and not wanting to trigger extra calls on load
this.mobileZipCode = serviceZipCodeFromQuestion;
} else {
await this.processMobileZipCodeChange();
}
},
async processMobileZipCodeChange() {
showIssLoadingModal(true);
await this.updateMobileZip();
if (!this.mobileZipError) {
const updateMobileZipServiceLocationObj = {
mobileProviderNumber: this.mobileProviderNumber,
provider: null,
refreshDatePicker: true,
zipCode: this.zipCode,
zipCodeCtu: this.zipCodeCtu
};
this.$emit('mobile-zip-updated', updateMobileZipServiceLocationObj);
} else {
this.$emit('clear-mobile-data');
showIssLoadingModal(false);
}
},
setCtuForMobile(val) {
this.zipCodeCtu = val;
},
@ -446,6 +482,10 @@ export default {
this.setServiceabilityDetails(initialData.serviceabilityDetails);
}
if (initialData.mobileFeePart) {
this.mobileFeePart = initialData.mobileFeePart;
}
if (initialData.providers) {
this.setMobileProviderNumber(initialData.providers.mobileProviderNumber);
let foundMatch = false;
@ -462,12 +502,22 @@ export default {
this.selectedProvider = null;
}
}
if (initialData.glassFees) {
useMainStore().getCombinedQuote(initialData.glassFees)
.then((combinedQuote) => {
this.setGlassFeeItems(combinedQuote);
});
}
},
setContainsMilitaryBase(val) {
if (this.zipContainsMilitaryBase !== val) {
this.zipContainsMilitaryBase = val;
}
},
setGlassFeeItems(newGlassFeeItems) {
this.mainStore.updateGlassFees(newGlassFeeItems);
},
setMobileProviderNumber(providerNumber) {
this.mobileProviderNumber = providerNumber;
},
@ -569,7 +619,23 @@ $page-side-padding: 1.5rem;
}
}
div.alert {
margin-top: 0.625rem;
margin-top: 1.25rem;
&.widget-name-AlertLowAvailabilityInshopWidget {
@include media-breakpoint-up(xs) {
:deep(.alert-headline-text-container) {
padding-left: 0.5rem;
padding-right: 0.375rem;
letter-spacing: 0.45px;
}
}
@include media-breakpoint-up(xl) {
:deep(.alert-headline-text-container) {
padding: 0rem 0.625rem;
}
}
}
button.looka-likea-link {
background: none;

View file

@ -1,9 +1,25 @@
import { mount } from '@vue/test-utils';
import crypto from 'crypto';
import serviceZipModalQuestion from '@/layouts/schedule-page/service-location/service-zip-modal-question/service-zip-modal-question.vue';
import { getMountOptions } from '@/helpers/unit-test-helper';
import { createTestingPinia } from '@pinia/testing';
import { useMainStore } from '@/store';
import settleAllPromises from '@/helpers/layout-helper';
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
import baseMixin from '@/mixins/base-mixin';
global.crypto = crypto;
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
// Mock fetchCmsContentForPage
jest.mock('@/helpers/cms-content-helper', () => ({
fetchCmsContentForPage: jest.fn()
}));
jest.mock('@/global-methods', () => ({
callHttpClient: jest.fn()
}));
jest.mock('@/digital-components/textbox-question/textbox-question', () => ({
getCmsContent: jest.fn((widgetName, cmsFieldName) => widgetName[cmsFieldName])
}));
@ -71,20 +87,48 @@ const mockModalCmsContent = {
FooterText: 'Sample modal footer text here.'
};
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
if (widgetName === linkWidgetName) {
return mockLinkCmsContent[cmsFieldName];
}
function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRunAfterInitializingStore = () => {}, queryString = {}) {
const mountOptions = getMountOptions({
router: {
navigate: jest.fn()
},
route: { query: { issPage: 'page-name', ...queryString }, params: {} }
});
if (widgetName === modalWidgetName) {
return mockModalCmsContent[cmsFieldName];
}
return null;
})
}
};
const testingPinia = createTestingPinia({
initialState: {
main: mainInitialState
}
});
useMainStore(testingPinia);
methodToRunAfterInitializingStore();
useMainStore().getServiceabilityDetails = jest.fn().mockReturnValue(mockGetServiceabilityDetails);
mountOptions.global.plugins = [testingPinia];
mountOptions.props = initialData
const apiResponses = { cmsContent: {} };
settleAllPromises.mockImplementation(() => apiResponses);
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
const wrapper = mount(serviceZipModalQuestion, mountOptions);
wrapper.vm.getCmsContent = jest.fn((widgetName, cmsFieldName) => {
if (widgetName === linkWidgetName) {
return mockLinkCmsContent[cmsFieldName];
}
if (widgetName === modalWidgetName) {
return mockModalCmsContent[cmsFieldName];
}
return null;
});
wrapper.vm.setCmsContent = jest.fn();
wrapper.vm.$router.navigateWithSpinner = jest.fn();
wrapper.vm.navigateBack = baseMixin.methods.navigateBack;
return { wrapper };
}
describe('service-zip-modal-question.vue', () => {
it('Initial state on load with existing location info', async () => {
@ -94,15 +138,13 @@ describe('service-zip-modal-question.vue', () => {
zipCode: '43235'
};
const wrapper = mount(serviceZipModalQuestion, {
mixins: [mockMixin],
props: {
const { wrapper } = getMountedComponent({},
{
modelValue: serviceZipCodeQuestion,
linkWidgetName,
modalWidgetName
},
attachTo: document.body
});
}
);
// Assert
expect(wrapper.html()).not.toEqual(expect.stringContaining(mockLinkCmsContent.BodyText));
@ -117,15 +159,13 @@ describe('service-zip-modal-question.vue', () => {
const zip = '43235';
const wrapper = mount(serviceZipModalQuestion, {
mixins: [mockMixin],
props: {
const { wrapper } = getMountedComponent({},
{
modelValue: serviceZipCodeQuestion,
mobileFeePart: {},
modalWidgetName
},
attachTo: document.body
});
}
);
// Act
wrapper.vm.internalModel.zipCode = zip;
@ -147,14 +187,12 @@ describe('service-zip-modal-question.vue', () => {
const zip = '';
const wrapper = mount(serviceZipModalQuestion, {
mixins: [mockMixin],
props: {
const { wrapper } = getMountedComponent({},
{
modelValue: serviceZipCodeQuestion,
modalWidgetName
},
attachTo: document.body
});
}
);
// Act
wrapper.vm.internalModel.zipCode = zip;
@ -173,14 +211,12 @@ describe('service-zip-modal-question.vue', () => {
const zip = '11111';
const wrapper = mount(serviceZipModalQuestion, {
mixins: [mockMixin],
props: {
const { wrapper } = getMountedComponent({},
{
modelValue: serviceZipCodeQuestion,
modalWidgetName
},
attachTo: document.body
});
}
);
// Act
wrapper.vm.internalModel.zipCode = zip;
@ -199,14 +235,12 @@ describe('service-zip-modal-question.vue', () => {
const zip = '';
const wrapper = mount(serviceZipModalQuestion, {
mixins: [mockMixin],
props: {
const { wrapper } = getMountedComponent({},
{
modelValue: serviceZipCodeQuestion,
modalWidgetName
},
attachTo: document.body
});
}
);
// Act
wrapper.vm.internalModel.zipCode = zip;
@ -226,14 +260,12 @@ describe('service-zip-modal-question.vue', () => {
const zip = '123';
const wrapper = mount(serviceZipModalQuestion, {
mixins: [mockMixin],
props: {
const { wrapper } = getMountedComponent({},
{
modelValue: serviceZipCodeQuestion,
modalWidgetName
},
attachTo: document.body
});
}
);
// Act
wrapper.vm.internalModel.zipCode = zip;

View file

@ -15,7 +15,6 @@
:onModalOpenedCallback="onModalOpened"
:onModalClosedCallback="onModalClosed"
:footerButtonText="modalFooterText"
:modalPosition="'center'"
@footerButtonEvent="setZipCode">
<serviceZipQuestion
ref="serviceZipCodeQuestion"
@ -38,8 +37,9 @@ import textLink from '@/ux-components/text-link/text-link.vue';
import serviceZipQuestion from '@/layouts/schedule-page/service-location/service-zip-question/service-zip-question.vue';
import modal from '@/digital-components/modal/modal.vue';
import alert from '@/ux-components/alert/alert.vue';
import { useMainStore } from '@/store';
import { getServiceabilityDetails, getZipCodeData } from '@/helpers/service-location-helper';
import { getZipCodeData } from '@/helpers/service-location-helper';
export default {
name: 'service-zip-modal-question',
@ -153,7 +153,7 @@ export default {
const serviceZipCode = this.internalModel.zipCode;
// retrieve serviceability details
const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode);
const serviceabilityDetails = await useMainStore().getServiceabilityDetails(serviceZipCode);
// update content related to service zip code
this.$emit('updated-serviceability', serviceabilityDetails.data);

View file

@ -7,7 +7,7 @@ describe('service-zip-question.vue', () => {
const text = '12345';
const wrapper = shallowMount(serviceZipQuestion, {
props: {
modelValue: text
parentZipCode: text
},
attachTo: document.body
});
@ -32,9 +32,9 @@ describe('service-zip-question.vue', () => {
// Act
wrapper.vm.internalZipcode = '55555';
wrapper.vm.updateModelValue();
wrapper.vm.emitZipCodeUpdate();
// Assert
expect(wrapper.emitted('update:modelValue')).toEqual([['55555']]);
expect(wrapper.emitted('service-zip-code-updated')).toEqual([['55555']]);
});
});

View file

@ -10,7 +10,7 @@
isRequired
:hasError="hasError"
:validationRules="`zip-required|${cmsWidgetName}-zip-format`"
@clickEvent="updateModelValue"
@clickEvent="emitZipCodeUpdate"
@fieldHasError="handleFieldError" />
</template>
@ -32,7 +32,7 @@ export default {
},
// TODO: Correct prop def.
props: {
modelValue: String,
parentZipCode: String,
cmsWidgetName: String,
serviceZipFormatErrorMessage: {
type: String,
@ -43,14 +43,14 @@ export default {
default: false
}
},
emits: ['update:modelValue', 'field-has-error'],
emits: ['service-zip-code-updated', 'field-has-error'],
data() {
return {
internalZipcode: this.modelValue
internalZipcode: this.parentZipCode
};
},
watch: {
modelValue(newValue) {
parentZipCode(newValue) {
if (newValue !== this.internalZipcode) {
this.internalZipcode = newValue;
}
@ -63,8 +63,8 @@ export default {
handleFieldError(hasError) {
this.$emit('field-has-error', hasError);
},
updateModelValue() {
this.$emit('update:modelValue', this.internalZipcode);
emitZipCodeUpdate() {
this.$emit('service-zip-code-updated', this.internalZipcode);
}
}
};

View file

@ -43,7 +43,8 @@
:headerText="modalHeaderText"
:onModalOpenedCallback="onModalOpened"
:footerButtonText="modalFooterText"
:modalPosition="modalPositions.center"
:closeLinkText="modalCloseText"
:displayCloseLink="true"
:allowInvalidSubmit="true"
@footerButtonEvent="updateSelectedProvider">
<googleMap
@ -68,17 +69,18 @@
groupName="chooseShop"
textPosition="text-start"
isRequired
validationRules="option-required" />
validationRules="provider-required" />
<span class="service-zip-prompt">
{{ serviceZipPrompt }}
</span>
<serviceZipQuestion
v-if="renderServiceZip"
:ref="SERVICE_ZIP_QUESTION_REF_NAME"
v-model="internalZipcode"
:parentZipCode="internalZipcode"
customInputId="serviceZipCode"
:cmsWidgetName="textboxQuestionWidgetName"
@fieldHasError="handleFieldError" />
@fieldHasError="handleFieldError"
@serviceZipCodeUpdated="handleServiceZipCodeChanged" />
<div
v-if="errorMessage"
ref="errorMessageDiv"
@ -87,6 +89,7 @@
class="d-inline-flex mt-0"
role="alert">{{ errorMessage }}</span>
</div>
<hr />
</modal>
</div>
</transition>
@ -110,12 +113,12 @@ import { toTitleCase } from '@/helpers/text-helper.js';
import { markRaw } from 'vue';
import { useMainStore } from '@/store';
import widgetFields from '@/constants/cms-widget-fields';
import { dropdownVariants, modalPositions } from '@/constants/component-variants';
import { dropdownVariants } from '@/constants/component-variants';
import errorMessages from '@/constants/error-messages';
import { defineRule } from 'vee-validate';
import { required } from '@/helpers/validation-rules';
defineRule('option-required', required(errorMessages.PROVIDER_REQUIRED));
defineRule('provider-required', required(errorMessages.PROVIDER_REQUIRED));
const SERVICE_ZIP_QUESTION_REF_NAME = 'serviceZipCodeQuestion';
@ -154,7 +157,6 @@ export default {
shopListButton: markRaw(shopListButton),
searchRadiusInMiles: '25',
nearbyShops: [],
modalPositions,
dropdownVariants,
renderServiceZip: true,
errorMessage: '',
@ -181,6 +183,9 @@ export default {
modalFooterText() {
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.FOOTER_TEXT);
},
modalCloseText() {
return this.getCmsContent(this.modalWidgetName, widgetFields.CONTENT_GROUP_WIDGET.BODY_TEXT);
},
modalName() {
return this.modalWidgetName;
},
@ -243,7 +248,7 @@ export default {
showIssLoadingModal(false);
}
},
methods: {
methods: {
async updateShops(autoExpand = false) {
this.errorMessage = '';
let result = await this.getNearbyShops(this.searchRadiusInMiles);
@ -333,6 +338,11 @@ export default {
this.errorMessage = '';
}
},
async handleServiceZipCodeChanged(serviceZipCodeFromQuestion) {
if (serviceZipCodeFromQuestion !== this.internalZipcode) {
this.internalZipcode = serviceZipCodeFromQuestion;
}
},
forceServiceZipRerender() {
this.renderServiceZip = false;
this.$nextTick(() => {
@ -405,5 +415,33 @@ export default {
margin-bottom: 1.25rem;
}
}
:deep(.modal-dialog) {
.modal-content {
hr {
margin: 1.375rem 0;
}
.modal-footer {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
align-items: flex-start;
button {
background-color: $green;
color: $white;
font-weight: 600;
width: 10.75rem;
&:hover {
background-color: darken($green, 10%);
}
}
a {
margin: .5rem 0 0;
}
}
}
}
}
</style>

View file

@ -426,7 +426,7 @@ export default {
`+${formatAmountInDollars(getPriceOfLineItem(this.premiumAppointmentFee))}`;
return {
// Unique value is required for each <input> and the premium appoinment shares an id
// Unique value is required for each <input> and the premium appointment shares an id
value: this.addPremiumFlagToInput(timeSlotData.id),
buttonLabel: this.premiumAppointmentButtonText,
buttonLabelSubCopy: formattedPrice,

View file

@ -22,6 +22,7 @@ import servicePackageRadio from '@/layouts/service-packages/service-package-ques
import partTypeStrings from '@/constants/part-type-strings';
import { useMainStore } from '@/store';
import { getPriceOfLineItem } from '@/helpers/price-calculator';
import { shallowRef } from 'vue';
const glassLocations = damageLocationsSelected;
@ -31,8 +32,6 @@ const packageNames = {
TIER_THREE: 'TierThree'
};
const store = useMainStore();
export default {
name: 'service-package-question',
components: {
@ -48,10 +47,14 @@ export default {
default: () => []
}
},
emits: ['vapsItemsSelected'],
setup() {
const mainStore = useMainStore();
return { mainStore };
},
emits: ['vapsItemsSelected', 'packageSelected'],
data() {
return {
servicePackageRadio,
servicePackageRadio: shallowRef(servicePackageRadio),
selectedPackageName: '',
questionText: 'Select an option:'
};
@ -97,7 +100,7 @@ export default {
return modifiedAnswers;
},
isRecalibrationOnOrder() {
return this.lineItemsContainsPartType(partTypeStrings.RECALIBRATION);
return this.mainStore.hasRecalibrationPart;
},
frontWipersApplicableForTierTwo() {
const frontWipersAreAvailable = this.lineItemsContainsPartType(partTypeStrings.FRONT_WIPER);
@ -147,7 +150,11 @@ export default {
selectedPackageName(newValue) {
const VapsProductsInSelectedPackage = this.getVapsLineItemsForSelectedPackage(newValue);
this.$emit('vapsItemsSelected', VapsProductsInSelectedPackage);
}
this.$emit('packageSelected', newValue);
},
availableLineItems() {
this.preselectPackage();
},
},
methods: {
processIfStatements,
@ -262,8 +269,19 @@ export default {
},
glassToReplaceContainsGlassLocation(glassLocation) {
const glassLocationMatches =
store.order.damage.glassToReplace?.filter((glassToReplace) => glassToReplace.glassLocation === glassLocation) ?? [];
this.mainStore.order.damage.glassToReplace?.filter((glassToReplace) => glassToReplace.glassLocation === glassLocation) ?? [];
return !!glassLocationMatches.length;
},
preselectPackage() {
const savedPackage = this.mainStore.order.servicePackage;
if (savedPackage && this.servicePackageAnswers.some((answer) => answer.value === savedPackage)) {
this.selectedPackageName = savedPackage;
return;
}
else {
this.selectedPackageName = '';
}
}
}
};
@ -278,6 +296,7 @@ export default {
@include media-breakpoint-up(md) {
:deep(.package-main) {
justify-content: center;
padding: 0 1.5rem 0 1.5rem;
.package-wrapper:nth-child(2) {
margin-left: 0.5rem;

View file

@ -10,11 +10,13 @@
</div>
<div class="iss-heritage-container-width">
<div class="service-packages-container">
<siteSubHeader
class="subheader mt-5"
justification="center"
cmsWidgetName="SiteSubHeaderWidget"
issContainingPage="service-packages" />
<h5
class="sub-header"
v-html="servicePackagesSubHeader"></h5>
<div
class="mb-4 text-center"
v-html="secondaryText">
</div>
<servicePackageQuestion
ref="servicePackage"
cmsWidgetName="ServicePackage"
@ -23,6 +25,7 @@
:validationRules="rules.optionRequired"
isRequired
@vapsItemsSelected="vapsItemsSelectedAction"
@packageSelected="packageSelectedAction"
@link-event="openModalAction" />
<div class="d-flex justify-content-center mt-8 mb-4">
<buttonMain
@ -55,10 +58,6 @@
<contentGroupModal
ref="RearWiperModal"
cmsWidgetName="RearWiperModal" />
<contentGroupModal
ref="RecalModal"
cssModalHeadlineClass="text-center"
cmsWidgetName="RecalModal" />
</div>
</Form>
</template>
@ -89,7 +88,6 @@ export default {
components: {
siteHeader,
siteFooter,
siteSubHeader,
Form,
servicePackageQuestion,
loadingModal,
@ -166,6 +164,7 @@ export default {
data() {
return {
selectedVaps: [],
selectedPackageTier: '',
availableLineItems: [],
loadingText: [
'Finding shops near you',
@ -182,6 +181,20 @@ export default {
computed: {
PriceDisclaimerText() {
return this.getCmsContent('PriceDisclaimerWidget', 'Text');
},
vehicleMake() {
return (store.order.vehicle.make ?? '').toUpperCase();
},
vehicleModel() {
return (store.order.vehicle.model ?? '').toUpperCase();
},
servicePackagesSubHeader() {
return this.getCmsContent('SiteSubHeaderWidget', 'SubHeaderText')
.replace('{custom:vehicleMake}', this.vehicleMake)
.replace('{custom:vehicleModel}', this.vehicleModel);
},
secondaryText() {
return this.getCmsContent('SiteSubHeaderWidget', 'SecondaryText');
}
},
methods: {
@ -204,6 +217,9 @@ export default {
vapsItemsSelectedAction(vapsItemsSelected) {
this.selectedVaps = vapsItemsSelected;
},
packageSelectedAction(packageTier) {
this.selectedPackageTier = packageTier;
},
forwardButtonAction() {
const parts = {
vaps: this.selectedVaps
@ -212,6 +228,7 @@ export default {
window.console.error('One or more items have no price assigned!');
}
store.updateVaps(this.selectedVaps);
store.updateServicePackage(this.selectedPackageTier);
const scenario = store.isMobileAppointment
? this.navigationScenarios.CLICKED_FORWARD_MOBILE
@ -244,7 +261,7 @@ export default {
padding-left: .9375rem;
padding-right: .9375rem;
display: block;
margin: 0 auto;
margin: 1.5rem auto;
:deep(.subheader-primary) {
display: block;
}
@ -274,4 +291,10 @@ export default {
.back-link {
margin-left: 1rem;
}
.sub-header {
text-align: center;
color: $black;
line-height: 2rem;
}
</style>

View file

@ -2,10 +2,6 @@
exports[`tpa-submit returns the initial data 1`] = `
Object {
"modalPositions": Object {
"center": "center",
"edge": "edge",
},
"sections": Array [],
"widget": Object {
"alertIncomplete": "AlertIncompleteWidget",

View file

@ -35,19 +35,18 @@
<modal
ref="recalModal"
class="recal-modal"
:modalPosition="modalPositions.center"
:headerText="recalModalContent.headerText"
:footerButtonText="recalModalContent.footerButtonText"
@footerButtonEvent="closeRecalModal">
<div class="recal-modal-subheader">
{{ recalModalContent.subHeaderText }}
</div>
{{ recalModalContent.subHeaderText }}
</div>
<img
class="recal-modal-image"
:src="recalModalContent.image" />
class="recal-modal-image"
:src="recalModalContent.image" />
<div
class="recal-modal-body"
v-html="recalModalContent.bodyText"></div>
class="recal-modal-body"
v-html="recalModalContent.bodyText"></div>
</modal>
<hr />
<div id="serviceSummarySection">
@ -122,7 +121,6 @@ import {
import { submitWorkOrder } from '@/helpers/order-helper.js';
import bailoutMessage from '@/constants/bailoutMessage';
import submitType from '@/constants/submit-type';
import { modalPositions } from '@/constants/component-variants';
const VERIFYING_COVERAGE = 'Verifying coverage';
@ -159,8 +157,7 @@ export default {
alertRecalWarning: 'AlertRecalWarningWidget',
editShopLinkText: 'EditShopLinkTextWidget',
contactDetails: 'ContactDetailsSectionWidget'
},
modalPositions
}
};
},
computed: {
@ -345,7 +342,7 @@ export default {
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.iss-heritage-container-width {
.tpa-submit-container {
position: relative;
@ -392,7 +389,7 @@ export default {
margin-bottom: .625rem;
}
.modal.recal-modal .modal-dialog {
:deep(.modal.recal-modal .modal-dialog) {
.modal-header {
justify-content: start;
margin: 0rem;
@ -403,6 +400,7 @@ export default {
line-height: 1.25rem;
text-align: start;
justify-content: start;
padding-left: 0rem;
}
.btn-close {
display: none;
@ -419,7 +417,29 @@ export default {
color: $black;
}
.recal-modal-image {
margin: 1.25rem 0rem
width: 100%;
height: auto;
margin: 1.25rem 0rem;
@include media-breakpoint-up(sm) {
width: 16.5rem;
}
@include media-breakpoint-up(md) {
width: 23.5rem;
}
}
.modal-footer {
padding-top: 0;
button {
background-color: $green;
color: $white;
font-weight: 600;
&:hover {
background-color: darken($green, 10%);
}
}
}
}
}

View file

@ -46,7 +46,8 @@
class="my-5"
cmsWidgetName="HasReplacementConflict"
alertClass="alert-danger"
:isDismissible="false" />
:isDismissible="false"
:isCollapsible="true" />
</div>
</div>
<div class="justify-content-center">

View file

@ -34,7 +34,8 @@
class="mt-5"
cmsWidgetName="SplitSingleConflict"
alertClass="alert-danger"
:isDismissible="false" />
:isDismissible="false"
:isCollapsible="true" />
</div>
</template>

View file

@ -46,18 +46,11 @@
:updateValues="updateStyleValues"
validationRules="style-required"
inputId="styleQuestionField" />
<alert
v-if="displayNoServiceAlert"
ref="AlertNoService"
class="mt-5"
cmsWidgetName="AlertVehicleSelection"
alertClass="alert-danger"
:isDismissable="false" />
<siteFooter
ref="siteFooter"
class="mt-5"
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid || displayNoServiceAlert"
:isForwardActionDisabled="!meta.valid"
@ForwardClicked="forwardButtonAction"
@backClicked="navigateBack" />
</div>
@ -72,7 +65,6 @@ import siteHeader from '@/iss-components/site-header/site-header.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import siteSubHeader from '@/iss-components/site-sub-header/site-sub-header.vue';
import vehicleQuestion from '@/layouts/vehicle-selection/vehicle-question/vehicle-question.vue';
import alert from '@/ux-components/alert/alert.vue';
import { useMainStore } from '@/store';
// Supporting files
@ -97,8 +89,7 @@ export default {
siteSubHeader,
siteFooter,
Form,
vehicleQuestion,
alert
vehicleQuestion
},
mixins: [baseFormMixin],
@ -131,15 +122,13 @@ export default {
selectedYear: year,
selectedMake: make,
selectedModel: model,
selectedStyle: style,
displayNoServiceAlert: false
selectedStyle: style
};
},
computed: {
},
watch: {
selectedYear(value) {
this.resetAlert();
this.mainStore.updateVehicleYear(value);
this.$refs.vehicleMakeQuestion.clearValues();
if (value) {
@ -147,7 +136,6 @@ export default {
}
},
selectedMake(value) {
this.resetAlert();
this.mainStore.updateVehicleMake(value);
this.$refs.vehicleModelQuestion.clearValues();
if (value) {
@ -155,7 +143,6 @@ export default {
}
},
selectedModel(value) {
this.resetAlert();
this.mainStore.updateVehicleModel(value);
this.$refs.vehicleStyleQuestion.clearValues();
if (value) {
@ -164,16 +151,7 @@ export default {
},
selectedStyle(value) {
if (value) {
this.resetAlert();
this.mainStore.updateVehicleStyle(value);
this.mainStore.setVehicle(
this.selectedYear,
this.selectedMake,
this.selectedModel,
this.selectedStyle
).then((result) => {
this.displayNoServiceAlert = !result.data.canSafeliteService;
});
}
}
},
@ -188,16 +166,6 @@ export default {
if (this.selectedModel) {
this.$refs.vehicleStyleQuestion.getNewValues();
}
if (this.selectedStyle) {
this.mainStore.setVehicle(
this.selectedYear,
this.selectedMake,
this.selectedModel,
this.selectedStyle
).then((result) => {
this.displayNoServiceAlert = !result.data.canSafeliteService;
});
}
},
methods: {
arePagePrerequisitesValid() {
@ -208,12 +176,23 @@ export default {
},
navigateForward() {
this.mainStore.setVehicle().then(() => {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.mainStore.setVehicle().then((result) => {
if (!result.data.canSafeliteService) {
this.mainStore.setBailout(bailoutMessage.HeavyTruckVehicle(this.mainStore.order.vehicle.carId));
}
if (this.mainStore.isBailout) {
return this.$router.navigate(
this.navigationScenarios.BAILOUT,
this.$route
);
}
return this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
}
);
},
async updateYearValues() {

View file

@ -67,17 +67,4 @@ describe('vin-lookup-alerts.vue', () => {
expect(twoIdenticalYMMVehicleAlert).toBeVisible();
});
test('NoServiceAlert is displayed on the screen', () => {
mountOptions.props = {
activeAlertType: vehicleLookupAlertTypes.NO_SERVICE
};
mountOptions.global.provide = {
vehicleFromLookup: {}
};
const { queryByRole } = render(VinLookupAlertsComponent, mountOptions);
const noServiceAlert = queryByRole('alert', { name: 'no-service-alert' });
expect(noServiceAlert).toBeVisible();
});
});

View file

@ -488,36 +488,6 @@ describe('vin-lookup.vue', () => {
);
});
});
test('Display no service alert if entered vin cannot be serviced', async () => {
const user = userEvent.setup();
mountOptions.global.stubs.vinQuestion = false;
mountOptions.data = () => ({
vinWithNonMatchingCarId: false,
isCarIdDifferentFromTheStore: false,
vin: mockValidVin
});
getPartsOrQuestions.mockResponse = vehicleWithNoAdditionalPartsOrQuestionsMockResponse;
jest.spyOn(VinLookupComponent.methods, lookupVehicleByVin.methodName)
.mockResolvedValue(lookupVehicleByVinCannotService.mockResponse);
jest.spyOn(vehicleQuestionsMixin.methods, getPartsOrQuestions.methodName)
.mockResolvedValue(getPartsOrQuestions.mockResponse);
const { container } = render(VinLookupComponent, mountOptions);
const vinInput = container.querySelector(vinInputSelector);
await user.type(vinInput, mockValidVin);
const continueButton = container.querySelector(continueButtonQuerySelector);
await user.click(continueButton);
await flushPromises();
await waitFor(() => {
expect(container).toContainHTML('noService');
});
});
test('Policy Vehicle with invalid vin corrected. Click "Continue", execute navigate with navigationScenario.CORRECTED_VIN_FROM_POLICY_VEHICLE', async () => {
const user = userEvent.setup();

View file

@ -170,6 +170,7 @@ export default {
// NOTE: If form is not valid, this method is not called when 'Continue' button is clicked
async forwardButtonAction() {
this.resetActiveAlert();
this.mainStore.resetBailout();
// Temp solution to reset the 'disabled' style on the Continue button
this.$refs.siteFooter.enableForwardAction();
showIssLoadingModal(true);
@ -179,11 +180,10 @@ export default {
const vehicleLookupResponse = await this.lookupVehicleByVin(this.vin);
if (!vehicleLookupResponse.data.canSafeliteService) {
this.activeVehicleLookupAlertType = vehicleLookupAlertTypes.NO_SERVICE;
this.mainStore.setBailout(bailoutMessage.HeavyTruckVehicle(vehicleLookupResponse.data.carId));
this.resetVehicleFromLookup();
this.$refs.siteFooter.disableForwardButton();
showIssLoadingModal(false);
return;
return this.navigateForward();
}
// Add vin bcs the response from the service doesn't contain vin

View file

@ -304,6 +304,7 @@ export default {
},
methods: {
async forwardButtonAction() {
this.mainStore.resetVehicleState();
this.mainStore.updatePolicyData(this.welcomePageModel);
// We want to await this separately. It's quick and if this has an invalid zip we don't want to be waiting on the slower API calls
@ -425,6 +426,8 @@ export default {
async loadReferralFromCookie() {
const issCookie = getISSCookie();
if (issCookie) {
this.$refs.continueModal.closeModal();
showIssLoadingModal(true);
const cookieReferral = {
referralNumber: issCookie.ReferralNumber,
responseDate: issCookie.ReferralDate,
@ -446,7 +449,7 @@ export default {
console.error(`Error on loading session from cookie ${error}`);
this.startNewReferral();
} finally {
this.$refs.continueModal.closeModal();
showIssLoadingModal(false);
}
}
},

View file

@ -46,38 +46,45 @@ export default {
return '';
},
logPageView(pageEvent) {
const store = useMainStore();
const currentPageName = this.getPageNameByQueryString();
const payload = {
userId: getDeviceIdValue(),
sessionKey: getSessionKeyValue(),
pageName: currentPageName,
referralSequenceNumber: store.order.referralSequenceNumber,
parentAccountNumber: store.order.parentAccountNumber ?? 0,
sessionId: getSessionIdValue(),
action: '',
event: pageEvent,
shouldUseSessionId: false,
experimentsForUser: useMainStore().applicationUser.experiments
experimentsForUser: store.applicationUser.experiments
};
useMainStore().logPageView(payload);
store.logPageView(payload);
},
logCustomEvent(category, action, label, value) {
const store = useMainStore();
const currentPageName = this.getPageNameByQueryString();
const payload = {
userId: getDeviceIdValue(),
sessionKey: getSessionKeyValue(),
pageName: currentPageName,
referralSequenceNumber: store.order.referralSequenceNumber,
parentAccountNumber: store.order.parentAccountNumber ?? 0,
sessionId: getSessionIdValue(),
category,
action,
label,
value,
shouldUseSessionId: false,
experimentsForUser: useMainStore().applicationUser.experiments
experimentsForUser: store.applicationUser.experiments
};
useMainStore().logCustomEvent(payload);
store.logCustomEvent(payload);
},
pushEventToGA(category, action, label, pushToLogApp = false, valueToLogType = null) {

View file

@ -361,6 +361,14 @@ export default {
const self = vm ?? this;
const currentPage = self.$route.query.issPage;
if (self.mainStore.isBailout) {
self.$router.navigate(
self.navigationScenarios.BAILOUT,
self.$route
);
return;
}
const hasPartQuestions = this.hasPartQuestions(partsOrQuestions);
const hasGlassLocationWithMultipleParts = this.hasGlassLocationWithMultipleParts(partsOrQuestions);
const hasChildPartQuestions = this.hasChildPartQuestions(partsOrQuestions);

View file

@ -19,6 +19,8 @@ const issPageValues = Object.freeze({
PAYMENT_METHOD: 'payment-method',
PAYMENT_PAGE: 'payment-page',
PAYMENT_RETURN: 'payment-return',
PAYMENT_PAGE_ADYEN: 'payment-page-adyen',
PAYMENT_RETURN_ADYEN: 'payment-return-adyen',
PART_QUESTIONS: 'part-questions',
POLICY_HOLDER_DETAILS: 'policy-holder-details',
PROVIDER_PREFERENCE: 'provider-preference',

View file

@ -102,10 +102,13 @@ const navigationScenarios = Object.freeze({
CLICKED_BACK_INSHOP: 'CLICKED_BACK_INSHOP',
CLICKED_BACK_MOBILE: 'CLICKED_BACK_MOBILE',
CLICKED_PAY_NOW: 'CLICKED_PAY_NOW',
CLICKED_PAY_NOW_ADYEN: 'CLICKED_PAY_NOW_ADYEN',
PAY_IN_ADVANCE_CANCEL: 'PAY_IN_ADVANCE_CANCEL',
PAY_IN_ADVANCE_ERROR: 'PAY_IN_ADVANCE_ERROR',
PAY_IN_ADVANCE_CREDIT_CARD_ERROR: 'PAY_IN_ADVANCE_CREDIT_CARD_ERROR',
PAY_IN_ADVANCE_SUCCESS: 'PAY_IN_ADVANCE_SUCCESS',
EDIT_SERVICE_LOCATION: 'EDIT_SERVICE_LOCATION',
EDIT_SERVICE_LOCATION_INSHOP: 'EDIT_SERVICE_LOCATION_INSHOP',
EDIT_SERVICE_LOCATION_MOBILE: 'EDIT_SERVICE_LOCATION_MOBILE',
EDIT_SCHEDULE: 'EDIT_SCHEDULE',
EDIT_WIPERS: 'EDIT_WIPERS',

View file

@ -14,6 +14,10 @@ const routingTable = () => [
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.POLICY_HOLDER_DETAILS
},
{
scenario: navigationScenarios.BAILOUT,
destinationIssPageValue: issPageValues.BAILOUT_PAGE
}
]
},
@ -638,9 +642,17 @@ const routingTable = () => [
destinationIssPageValue: issPageValues.PAYMENT_PAGE
},
{
scenario: navigationScenarios.EDIT_SERVICE_LOCATION,
scenario: navigationScenarios.CLICKED_PAY_NOW_ADYEN,
destinationIssPageValue: issPageValues.PAYMENT_PAGE_ADYEN
},
{
scenario: navigationScenarios.EDIT_SERVICE_LOCATION_INSHOP,
destinationIssPageValue: issPageValues.SCHEDULE_PAGE
},
{
scenario: navigationScenarios.EDIT_SERVICE_LOCATION_MOBILE,
destinationIssPageValue: issPageValues.CONTACT_DETAILS
},
{
scenario: navigationScenarios.EDIT_SCHEDULE,
destinationIssPageValue: issPageValues.SCHEDULE_PAGE
@ -668,6 +680,23 @@ const routingTable = () => [
}
]
},
{
issPageValue: issPageValues.PAYMENT_PAGE_ADYEN,
maps: [
{
scenario: navigationScenarios.CLICKED_BACK,
destinationIssPageValue: issPageValues.PAYMENT_METHOD
},
{
scenario: navigationScenarios.CLICKED_FORWARD,
destinationIssPageValue: issPageValues.ORDER_CONFIRMATION
},
{
scenario: navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
destinationIssPageValue: issPageValues.ORDER_CONFIRMATION
}
]
},
{
issPageValue: issPageValues.PAYMENT_RETURN,
maps: [
@ -685,6 +714,23 @@ const routingTable = () => [
}
]
},
{
issPageValue: issPageValues.PAYMENT_RETURN_ADYEN,
maps: [
{
scenario: navigationScenarios.PAY_IN_ADVANCE_CANCEL,
destinationIssPageValue: issPageValues.PAYMENT_PAGE_ADYEN
},
{
scenario: navigationScenarios.PAY_IN_ADVANCE_ERROR,
destinationIssPageValue: issPageValues.PAYMENT_PAGE_ADYEN
},
{
scenario: navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
destinationIssPageValue: issPageValues.ORDER_CONFIRMATION
}
]
},
{
issPageValue: issPageValues.TPA_CONFIRMATION,
maps: [

View file

@ -155,6 +155,7 @@ export const getDefaultState = () => ({
vaps: null,
serverData: null
},
servicePackage: null,
insuranceCoverage: {
coverageStatus: coverageStatuses.PENDING,
coverageType: coverageType.NONE,
@ -1253,7 +1254,7 @@ export const useMainStore = defineStore({
},
async getCombinedQuote(availableLineItems) {
if (!availableLineItems || availableLineItems.length === 0) {
if (!availableLineItems || availableLineItems.length === 0 || !this.providerNumber) {
return [];
}
@ -1777,6 +1778,9 @@ export const useMainStore = defineStore({
updatePaypalToken(token) {
this.order.payment.paypalToken = token;
},
updateNextGenSettledAmount(amount) {
this.order.payment.nextGenSettledAmount = amount;
},
setSaveSessionPromise(promise) {
this.applicationUser.saveSessionPromise = promise;
},
@ -1842,7 +1846,7 @@ export const useMainStore = defineStore({
this.order.serviceLocation.zipCodeCtu = serviceLocationInfo.zipCodeCtu ?? this.order.serviceLocation.zipCodeCtu;
this.order.serviceLocation.appointmentType = serviceLocationInfo.appointmentType ?? this.order.serviceLocation.appointmentType;
this.order.serviceLocation.isVehicleProtected = serviceLocationInfo.isVehicleProtected ?? this.order.serviceLocation.isVehicleProtected;
this.updateServiceLocationProvider(serviceLocationInfo.provider)
this.updateServiceLocationProvider(serviceLocationInfo.provider);
this.order.serviceLocation.tpaSearchRadius = serviceLocationInfo.tpaSearchRadius;
},
updateAppointmentType(appointmentType) {
@ -1865,7 +1869,6 @@ export const useMainStore = defineStore({
resetState() {
Object.assign(this, getDefaultState());
},
resetRegistrationState() {
this.order.vehicle.registration.licensePlate = null;
this.order.vehicle.registration.address = null;
@ -1904,14 +1907,12 @@ export const useMainStore = defineStore({
this.order.serviceLocation.state = null;
this.order.serviceLocation.isVehicleProtected = null;
},
resetBailout() {
this.updatePageData({
page: issPageValues.BAILOUT_PAGE,
data: null
});
},
resetInsurance() {
this.order.insuranceCoverage.coverageStatus = coverageStatuses.PENDING;
this.order.insuranceCoverage.coverageType = coverageType.NONE;
@ -1943,14 +1944,18 @@ export const useMainStore = defineStore({
// Process Recycle Fee
this.updateRecycleFee(partsData.find((rf) => rf.partNumber === partNumberStrings.RECYCLE_FEE));
// Remove RecycleFee from supported items as it's saved in feeItems;
this.order.lineItems.supportingItems = partsData.filter((i) => i.partNumber !== partNumberStrings.RECYCLE_FEE);
// Remove RecycleFee and Labor2 from supporting items as they're saved in feeItems;
this.order.lineItems.supportingItems = partsData.filter((i) => i.partNumber !== partNumberStrings.RECYCLE_FEE && i.partNumber !== partNumberStrings.LABOR2);
},
updateVaps(partsData) {
this.order.lineItems.vaps = partsData;
},
updateServicePackage(packageName) {
this.order.servicePackage = packageName;
},
updateMobileFee(fee) {
const isMobileFeeHidden = getExperimentSettingValue(this.experimentSettings, experimentSettings.ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_HIDDEN) === 'true';
// Mobile Fee is only added for NO COMP or ITAC and is NOT hidden
@ -2035,6 +2040,7 @@ export const useMainStore = defineStore({
this.updateSupportingItems(null);
this.updateVaps(null);
this.updateServicePackage(null);
},
updateVehicleCoverage(coverage) {
@ -2079,6 +2085,7 @@ export const useMainStore = defineStore({
this.order.loadedFromDupeCheck = null;
this.order.loadedSessionClearedPreviousData = null;
this.order.availableVaps = null;
this.order.servicePackage = null;
this.order.carrierPhoneNumber = null;
this.order.customerPortalLoginToken = null;
this.order.loadedFromCookie = false;
@ -2364,6 +2371,7 @@ export const useMainStore = defineStore({
this.updateCapabilityQuestionAnswers(null);
this.updateSupportingItems(null);
this.updateVaps(null);
this.updateServicePackage(null);
this.updatePageData({ page: issPageValues.VEHICLE_PARTS, data: null });
this.updatePageData({ page: issPageValues.MOLDING_QUESTIONS, data: null });
@ -2519,12 +2527,14 @@ export const useMainStore = defineStore({
this.logExperimentExposure(pageName, experiment);
}
},
logPageView({ userId, sessionKey, pageName, sessionId, action, event, shouldUseSessionId, experimentsForUser }) {
logPageView({ userId, sessionKey, pageName, referralSequenceNumber, parentAccountNumber, sessionId, action, event, shouldUseSessionId, experimentsForUser }) {
const payload = {
userId,
sessionKey,
sessionId,
pageName,
referralSequenceNumber,
parentAccountNumber,
applicationName: applicationConfig.APPLICATION_NAME,
action,
event,
@ -2536,7 +2546,8 @@ export const useMainStore = defineStore({
method: endpoints.LogPageView.method,
endpoint: endpoints.LogPageView.url,
payload,
logApiCall: false
logApiCall: false,
bailoutOnError: false
}).then(
(response) => response,
(error) => {
@ -2565,7 +2576,8 @@ export const useMainStore = defineStore({
method: endpoints.LogCustomEvent.method,
endpoint: endpoints.LogCustomEvent.url,
payload,
logApiCall: false
logApiCall: false,
bailoutOnError: false
}).then(
(response) => response,
(error) => {
@ -2593,7 +2605,8 @@ export const useMainStore = defineStore({
method: endpoints.InitializeSession.method,
endpoint: endpoints.InitializeSession.url,
payload,
logApiCall: false
logApiCall: false,
bailoutOnError: false
}).then(
(response) => response,
(error) => {
@ -2832,17 +2845,20 @@ export const useMainStore = defineStore({
this.resetRegistrationState();
this.resetGlassPartsState();
this.updateVaps(null);
this.updateServicePackage(null);
},
resetDamageAndDependencies() {
this.resetDamageState();
this.resetGlassPartsState();
this.updateVaps(null);
this.updateServicePackage(null);
},
resetPartsAndDependencies() {
this.resetGlassPartsState();
this.updateVaps(null);
this.updateServicePackage(null);
this.resetServiceLocationAndDependencies();
},
@ -2883,6 +2899,24 @@ export const useMainStore = defineStore({
endpoint: endpoints.GetPaymentSignature.url
});
},
async initializeAdyenPayment(requestBody) {
const response = await globalMethods.callHttpClient({
method: endpoints.InitializeAdyenPayment.method,
endpoint: endpoints.InitializeAdyenPayment.url,
payload: requestBody,
logApiCall: true
});
return response.data;
},
async getAdyenSessionResult(paymentSessionRequest) {
const response = await globalMethods.callHttpClient({
method: endpoints.GetAdyenSessionResult.method,
endpoint: endpoints.GetAdyenSessionResult.url,
payload: paymentSessionRequest,
logApiCall: true
});
return response.data;
},
hasSubmittedOrder() {
return window.sessionStorage.getItem(webStorageConstants.SUBMITTED_ORDER) !== null;

View file

@ -2489,4 +2489,32 @@ describe('Store', () => {
expect(store.order.contactInfo.alternativePhone).toBeUndefined();
});
});
describe('updateSupportingItems method', () => {
it('updateSupportingItems removes Recyle Fee and Labor2 Fee from supporting items in store', () => {
// Arrange
const partsData = [
{
partNumber: 'RECYCLE FEE',
partType: 'REPLACE FEE'
},
{
partNumber: 'LABOR2',
partType: 'NON_GLASS'
}
];
// Act
store.updateSupportingItems(partsData);
// Assert
expect.not.objectContaining({
partNumber: 'RECYCLE FEE',
partType: 'REPLACE FEE'
});
expect.not.objectContaining({
partNumber: 'LABOR2',
partType: 'NON_GLASS'
});
});
});
});

View file

@ -221,7 +221,7 @@ $alert-red-color: $red;
//Modal animation
// This affects all [Bootstrap] modals
$modal-fade-transform: translate(0, 100%);
$modal-fade-transform: translate(0, -25%);
$modal-backdrop-opacity: 0;
//Disable default !important behavior

View file

@ -106,6 +106,7 @@
:text="getRouterLinkDisplayTextFromCopy(copy)"
href="#!"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
:preventDefaultOnClick="preventDefaultOnClick"
@clickEvent="$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))" />
</span>
<span
@ -172,7 +173,8 @@ export default {
startCollapsed: {
type: Boolean,
default: false
}
},
preventDefaultOnClick: { type: Boolean, required: false, default: false }
},
emits: ['textLinkClicked'],
data() {

View file

@ -192,4 +192,37 @@ describe('buttonMain.vue', () => {
// Expect
expect(spy).not.toHaveBeenCalled();
});
describe('Button Types', () => {
it('Should set button type to submit', async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
buttonType: 'submit'
}
})
);
// Assert
const button = wrapper.find('button');
// Expect
expect(button.attributes('type')).toEqual('submit');
});
it('Should default button type to button', async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks()
);
// Assert
const button = wrapper.find('button');
// Expect
expect(button.attributes('type')).toEqual('button');
});
});
});

View file

@ -3,6 +3,7 @@
:aria-disabled="isDisabled"
class="btn d-flex align-items-center justify-content-center delay"
:class="[getVariant, `btn-${size}`, canOverride ? 'btn-override' : '']"
:type="buttonType"
@click="clicked">
<span class="m-0">{{ buttonText }}</span>
</button>
@ -21,7 +22,9 @@ export default {
isDisabled: Boolean,
isOutline: Boolean,
pushToGA: Boolean,
canOverride: { type: Boolean, required: false, default: true }
canOverride: { type: Boolean, required: false, default: true },
preventDefaultOnClick: { type: Boolean, required: false, default: false },
buttonType: { type: String, required: false, default: 'button', validator: (val) => ['button', 'submit', 'reset'].includes(val) }
},
emits: ['click-event'],
computed: {
@ -34,8 +37,11 @@ export default {
}
},
methods: {
clicked() {
clicked(e) {
if (!this.isDisabled) {
if (this.preventDefaultOnClick) {
e.preventDefault();
}
if (this.pushToGA) {
this.pushEventToGA(
this.$route.query[this.queryStrings.ISS_PAGE],

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