Merge pull request #238 from Safelite/feature/digital/SSR-346
Feature/digital/ssr 346
This commit is contained in:
commit
5efbb2e5af
19 changed files with 1048 additions and 523 deletions
|
|
@ -1,68 +1,255 @@
|
|||
jest.mock("vee-validate", () => ({
|
||||
useForm: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockValidate = (returnValue) => jest.fn(async () => Promise.resolve({ valid: returnValue }));
|
||||
const mockMeta = (returnValue) => jest.fn(async () => Promise.resolve(returnValue));
|
||||
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import Modal from "./modal";
|
||||
import modal from "./modal";
|
||||
import crypto from "crypto";
|
||||
import { useForm } from "vee-validate";
|
||||
import { Modal } from "bootstrap";
|
||||
|
||||
const footerButtonText = "Sample footer text here.";
|
||||
const headerText = "Sample header text here.";
|
||||
|
||||
global.crypto = crypto;
|
||||
|
||||
describe("modal.vue", () => {
|
||||
it("Should display header text when HeaderText is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(Modal, {
|
||||
mixins: [mockMixin],
|
||||
it("Should display modal header text when headerText is defined", async () => {
|
||||
// Arrange / Act
|
||||
const fakeMeta = {
|
||||
touched: true,
|
||||
dirty: true,
|
||||
valid: true,
|
||||
validated: true,
|
||||
};
|
||||
|
||||
useForm.mockReturnValue({
|
||||
meta: mockMeta(fakeMeta),
|
||||
validate: mockValidate(true),
|
||||
});
|
||||
|
||||
const wrapper = shallowMount(modal, {
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
headerText: headerText,
|
||||
footerButtonText: footerButtonText,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"]));
|
||||
|
||||
// Assert
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(headerText));
|
||||
});
|
||||
|
||||
it("Should display subheader text when SubheaderText is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(Modal, {
|
||||
mixins: [mockMixin],
|
||||
it("Should display footer button text when footerButtonText is defined", async () => {
|
||||
// Arrange / Act
|
||||
const fakeMeta = {
|
||||
touched: true,
|
||||
dirty: true,
|
||||
valid: true,
|
||||
validated: true,
|
||||
};
|
||||
|
||||
useForm.mockReturnValue({
|
||||
meta: mockMeta(fakeMeta),
|
||||
validate: mockValidate(true),
|
||||
});
|
||||
const wrapper = shallowMount(modal, {
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
footerButtonText: footerButtonText,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["SubheaderText"]));
|
||||
|
||||
// Assert
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(footerButtonText));
|
||||
});
|
||||
|
||||
it("Should insert image url when Image is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(Modal, {
|
||||
mixins: [mockMixin],
|
||||
it("Should emit 'footer-button-event' if the form is valid", async () => {
|
||||
// Arrange
|
||||
const fakeMeta = {
|
||||
touched: true,
|
||||
dirty: true,
|
||||
valid: true,
|
||||
validated: true,
|
||||
};
|
||||
|
||||
useForm.mockReturnValue({
|
||||
meta: mockMeta(fakeMeta),
|
||||
validate: mockValidate(true),
|
||||
});
|
||||
|
||||
const resetButtonStyle = jest.fn();
|
||||
const wrapper = shallowMount(modal, {
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
footerButtonText: footerButtonText,
|
||||
headerText: headerText,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Image"]));
|
||||
wrapper.vm.resetButtonStyle = resetButtonStyle;
|
||||
|
||||
// Act
|
||||
const buttonMain = wrapper.findComponent({ ref: "modalButtonMain" });
|
||||
await buttonMain.trigger("click-event");
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted("footer-button-event")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Should display body text when BodyText is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(Modal, {
|
||||
mixins: [mockMixin],
|
||||
it("Should not emit 'footer-button-event' if the form is invalid", async () => {
|
||||
// Arrange
|
||||
const fakeMeta = {
|
||||
touched: true,
|
||||
dirty: true,
|
||||
valid: false,
|
||||
validated: true,
|
||||
};
|
||||
|
||||
useForm.mockReturnValue({
|
||||
meta: mockMeta(fakeMeta),
|
||||
validate: mockValidate(false),
|
||||
});
|
||||
|
||||
const resetButtonStyle = jest.fn();
|
||||
const wrapper = shallowMount(modal, {
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
footerButtonText: footerButtonText,
|
||||
headerText: headerText,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"]));
|
||||
wrapper.vm.resetButtonStyle = resetButtonStyle;
|
||||
|
||||
// Act
|
||||
const buttonMain = wrapper.findComponent({ ref: "modalButtonMain" });
|
||||
await buttonMain.trigger("click-event");
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted("footer-button-event")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("Should have a disabled footer button when the form has not been touched", async () => {
|
||||
// Arrange / Act
|
||||
const fakeMeta = {
|
||||
touched: true,
|
||||
dirty: true,
|
||||
valid: true,
|
||||
validated: true,
|
||||
};
|
||||
|
||||
useForm.mockReturnValue({
|
||||
meta: mockMeta(fakeMeta),
|
||||
validate: mockValidate(true),
|
||||
});
|
||||
|
||||
const resetButtonStyle = jest.fn();
|
||||
const wrapper = shallowMount(modal, {
|
||||
props: {
|
||||
footerButtonText: footerButtonText,
|
||||
headerText: headerText,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
wrapper.vm.resetButtonStyle = resetButtonStyle;
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
|
||||
});
|
||||
|
||||
it("Should have a disabled footer button when the form is invalid", async () => {
|
||||
// Arrange / Act
|
||||
const fakeMeta = {
|
||||
touched: true,
|
||||
dirty: true,
|
||||
valid: true,
|
||||
validated: true,
|
||||
};
|
||||
|
||||
useForm.mockReturnValue({
|
||||
meta: mockMeta(fakeMeta),
|
||||
validate: mockValidate(true),
|
||||
});
|
||||
|
||||
const resetButtonStyle = jest.fn();
|
||||
const wrapper = shallowMount(modal, {
|
||||
props: {
|
||||
footerButtonText: footerButtonText,
|
||||
headerText: headerText,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
wrapper.vm.resetButtonStyle = resetButtonStyle;
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
|
||||
});
|
||||
|
||||
it("Should call bootstrap Modal method 'show' when calling 'openModal'", async () => {
|
||||
// Arrange
|
||||
const fakeMeta = {
|
||||
touched: true,
|
||||
dirty: true,
|
||||
valid: true,
|
||||
validated: true,
|
||||
};
|
||||
|
||||
useForm.mockReturnValue({
|
||||
meta: mockMeta(fakeMeta),
|
||||
validate: mockValidate(true),
|
||||
});
|
||||
|
||||
const showMock = jest.spyOn(Modal.prototype, "show");
|
||||
|
||||
const resetButtonStyle = jest.fn();
|
||||
const wrapper = shallowMount(modal, {
|
||||
props: {
|
||||
footerButtonText: footerButtonText,
|
||||
headerText: headerText,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
wrapper.vm.resetButtonStyle = resetButtonStyle;
|
||||
|
||||
// Act
|
||||
wrapper.vm.openModal();
|
||||
|
||||
// Assert
|
||||
expect(showMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Should call bootstrap Modal method 'hide' when calling 'closeModal'", async () => {
|
||||
// Arrange
|
||||
const fakeMeta = {
|
||||
touched: true,
|
||||
dirty: true,
|
||||
valid: true,
|
||||
validated: true,
|
||||
};
|
||||
|
||||
useForm.mockReturnValue({
|
||||
meta: mockMeta(fakeMeta),
|
||||
validate: mockValidate(true),
|
||||
});
|
||||
const hideMock = jest.spyOn(Modal.prototype, "hide");
|
||||
|
||||
const resetButtonStyle = jest.fn();
|
||||
const wrapper = shallowMount(modal, {
|
||||
props: {
|
||||
footerButtonText: footerButtonText,
|
||||
headerText: headerText,
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
wrapper.vm.resetButtonStyle = resetButtonStyle;
|
||||
|
||||
// Act
|
||||
wrapper.vm.openModal();
|
||||
wrapper.vm.closeModal();
|
||||
|
||||
// Assert
|
||||
expect(hideMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
return mockCmsContent[cmsFieldName];
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const mockCmsContent = {
|
||||
HeaderText: "Sample header text here.",
|
||||
SubheaderText: "Sample subheader text here.",
|
||||
Image: "https://www.sampleImage.sample",
|
||||
BodyText: "Sample body text here.",
|
||||
FooterText: "Sample footer text here.",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,39 +2,38 @@
|
|||
<!-- Modal -->
|
||||
<div
|
||||
class="modal fade modal-component"
|
||||
v-on="{ 'hidden.bs.modal': resetButtonStyle }"
|
||||
:id="cmsWidgetName"
|
||||
v-on="{ 'hidden.bs.modal': onModalClosed, 'shown.bs.modal': onModalOpened }"
|
||||
:id="modalId"
|
||||
tabindex="-1"
|
||||
aria-labelledby="ModalComponentLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header mb-2 mt-2">
|
||||
<label
|
||||
v-if="headerText"
|
||||
class="modal-title d-flex justify-content-center pb-0 w-100">
|
||||
{{ headerText }}
|
||||
</label>
|
||||
<button
|
||||
ref="closeButton"
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
@mousedown="closeModal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body ps-4 pe-4 pt-5 pb-4">
|
||||
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
|
||||
<h5 class="mb-4" v-html="ModalHeadline"></h5>
|
||||
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p>
|
||||
<p class="mb-0" v-html="ModalBodyText"></p>
|
||||
<p
|
||||
class="my-4 caption modal-sub-body"
|
||||
v-if="ModalSubBodyText"
|
||||
v-html="ModalSubBodyText"></p>
|
||||
<div class="modal-body ps-5 pe-5 pb-4 pt-0">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="modal-footer px-5 py-4">
|
||||
<buttonMain
|
||||
<modalButtonMain
|
||||
isPrimary
|
||||
class="w-100"
|
||||
ref="buttonMain"
|
||||
suppressLoader
|
||||
:buttonText="ModalCloseButtonText"
|
||||
@click-event="buttonClick"
|
||||
data-bs-dismiss="modal" />
|
||||
ref="modalButtonMain"
|
||||
loaderColor="white"
|
||||
:buttonText="footerButtonText"
|
||||
@click-event="validateAndEmit"
|
||||
:class="isFooterButtonDisabled && 'form-test-invalid'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -42,46 +41,80 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import buttonMain from "@/ux-components/button-main/button-main";
|
||||
import modalButtonMain from "@/ux-components/modal-button-main/modal-button-main";
|
||||
import { Modal } from "bootstrap";
|
||||
import { useForm } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "modal",
|
||||
props: {
|
||||
cmsWidgetName: String,
|
||||
modalId: String,
|
||||
headerText: String,
|
||||
footerButtonText: String,
|
||||
onModalOpenedCallback: {
|
||||
type: Function,
|
||||
},
|
||||
onModalClosedCallback: {
|
||||
type: Function,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
ModalHeadline() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
|
||||
},
|
||||
ModalSubheadertext() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
||||
},
|
||||
ModalBodyText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "BodyText");
|
||||
},
|
||||
ModalSubBodyText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "BodyText2");
|
||||
},
|
||||
ModalImage() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "Image");
|
||||
},
|
||||
ModalCloseButtonText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||
},
|
||||
setup(props) {
|
||||
const modalId = props.modalId ? props.modalId : `modal-${crypto.randomUUID()}`;
|
||||
|
||||
const { meta, validate, resetForm } = useForm();
|
||||
|
||||
return {
|
||||
modalId,
|
||||
meta,
|
||||
validate,
|
||||
resetForm,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async validateAndEmit() {
|
||||
const validationResult = await this.validate();
|
||||
if (validationResult.valid) {
|
||||
this.$emit("footer-button-event");
|
||||
} else {
|
||||
this.resetButtonStyle();
|
||||
}
|
||||
},
|
||||
resetButtonStyle() {
|
||||
this.$refs.buttonMain.resetButtonStyle();
|
||||
this.$refs.modalButtonMain.resetButtonStyle();
|
||||
},
|
||||
onModalOpened() {
|
||||
this.onModalOpenedCallback?.();
|
||||
},
|
||||
onModalClosed() {
|
||||
this.resetButtonStyle();
|
||||
this.onModalClosedCallback?.();
|
||||
},
|
||||
openModal() {
|
||||
const modal = Modal.getOrCreateInstance(document.getElementById(this.modalId));
|
||||
modal?.show();
|
||||
},
|
||||
closeModal() {
|
||||
const modal = Modal.getInstance(document.getElementById(this.modalId));
|
||||
modal?.hide();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isFooterButtonDisabled() {
|
||||
if (!this.meta.touched) {
|
||||
return !this.meta.valid;
|
||||
}
|
||||
return !this.meta.dirty || !this.meta.valid;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonMain,
|
||||
modalButtonMain,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal {
|
||||
overflow: hidden;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
h5,
|
||||
|
|
@ -92,9 +125,17 @@ export default {
|
|||
.modal-header {
|
||||
border-bottom: none;
|
||||
.btn-close {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.6874 1.82179L9.50889 8L15.6874 14.1782C16.1042 14.595 16.1042 15.2707 15.6874 15.6875C15.4843 15.8907 15.2146 16 14.9328 16C14.6514 16 14.3818 15.8906 14.1787 15.6875L8.00017 9.50934L1.82171 15.6875C1.6182 15.8907 1.34876 16 1.06708 16C0.785733 16 0.516056 15.8906 0.312965 15.6875C-0.10429 15.2706 -0.10429 14.5952 0.312797 14.1784L6.03276 8.45867L6.49146 8L0.312945 1.82177C-0.10429 1.40483 -0.10429 0.729418 0.312797 0.31262C0.728936 -0.104145 1.40489 -0.104051 1.82185 0.31262L7.54152 6.03202L8.00017 6.49065L14.1786 0.312472C14.5955 -0.104107 15.2707 -0.104201 15.6874 0.312452C16.1042 0.729289 16.1042 1.40496 15.6874 1.82179Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-weight: 500;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
&.modal-component {
|
||||
.modal-dialog {
|
||||
|
|
@ -141,4 +182,4 @@ body {
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -173,9 +173,14 @@ function mapStringToModal(str) {
|
|||
|
||||
let params = linkToReplace.substring((dynamicStrings.MODAL_LINK).length + 2, linkToReplace.length -1)
|
||||
let splitParams = params.split(",");
|
||||
let bodyText = '<a href="#!" data-bs-toggle="modal" data-bs-target="#' + splitParams[0] + '" aria-label="Modal window">' + splitParams[1] +'</a>'
|
||||
|
||||
return str.replace(linkToReplace, bodyText);
|
||||
|
||||
let bodyText = '<a modalTarget="' + splitParams[0] + '" class="modal-text" aria-label="Modal window">' + splitParams[1] + '</a>'
|
||||
let returnVal = str.replace(linkToReplace, bodyText)
|
||||
|
||||
if (returnVal.includes(dynamicStrings.MODAL_LINK)) {
|
||||
returnVal = mapStringToModal(returnVal);
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
// Function to convert a string, into a matching global state item.
|
||||
|
|
@ -214,7 +219,20 @@ function mapStringToState(str) {
|
|||
}
|
||||
|
||||
return stringBuilder.trimStart();
|
||||
}
|
||||
}
|
||||
|
||||
export function setupModalLinks(context) {
|
||||
context.$nextTick(() => {
|
||||
const elements = document.getElementsByClassName("modal-text")
|
||||
for(let element of elements){
|
||||
const target = element.getAttribute("modalTarget");
|
||||
if(target)
|
||||
{
|
||||
element.addEventListener("click", () => context.$refs[target].openModal() );
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function doesCopyContainRouterLink(copy) {
|
||||
return copy.includes(this.dynamicStrings.ROUTER_LINK);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ export function getMountOptions(mockData) {
|
|||
mocks.queryStrings = queryStrings;
|
||||
mocks.$router = mockData?.router;
|
||||
mocks.$route = mockData?.route;
|
||||
mocks.GaActions = GaActions;
|
||||
mocks.pushEventToGA = jest.fn();
|
||||
mocks.$loadScript = mockData?.loadScript;
|
||||
mocks.prependActionToMethod = jest.fn();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import { mount } from "@vue/test-utils";
|
||||
import contentGroupModal from "./content-group-modal";
|
||||
import crypto from "crypto";
|
||||
|
||||
global.crypto = crypto;
|
||||
|
||||
describe("content-group-modal.vue", () => {
|
||||
it("Should display header text when HeaderText is defined in the CMS", async () => {
|
||||
const wrapper = mount(contentGroupModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"]));
|
||||
});
|
||||
|
||||
it("Should display subheader text when SubheaderText is defined in the CMS", async () => {
|
||||
const wrapper = mount(contentGroupModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["SubheaderText"]));
|
||||
});
|
||||
|
||||
it("Should insert image url when Image is defined in the CMS", async () => {
|
||||
const wrapper = mount(contentGroupModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Image"]));
|
||||
});
|
||||
|
||||
it("Should display body text when BodyText is defined in the CMS", async () => {
|
||||
const wrapper = mount(contentGroupModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"]));
|
||||
});
|
||||
});
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
return mockCmsContent[cmsFieldName];
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const mockCmsContent = {
|
||||
HeaderText: "Sample header text here.",
|
||||
SubheaderText: "Sample subheader text here.",
|
||||
Image: "https://www.sampleImage.sample",
|
||||
BodyText: "Sample body text here.",
|
||||
FooterText: "Sample footer text here.",
|
||||
};
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<modal
|
||||
:ref="ModalName"
|
||||
:modalId="ModalName"
|
||||
:footerButtonText="ModalCloseButtonText"
|
||||
@footer-button-event="footerButtonClick">
|
||||
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
|
||||
<h5 class="mb-4" v-html="ModalHeadline"></h5>
|
||||
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p>
|
||||
<p class="mb-0" v-html="ModalBodyText"></p>
|
||||
<p
|
||||
class="my-4 caption modal-sub-body"
|
||||
v-if="ModalSubBodyText"
|
||||
v-html="ModalSubBodyText"></p>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modal from "@/digital-components/modal/modal";
|
||||
|
||||
export default {
|
||||
name: "content-group-modal",
|
||||
props: {
|
||||
cmsWidgetName: String,
|
||||
},
|
||||
computed: {
|
||||
ModalName() {
|
||||
return this.cmsWidgetName;
|
||||
},
|
||||
ModalHeadline() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
|
||||
},
|
||||
ModalSubheadertext() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
||||
},
|
||||
ModalBodyText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "BodyText");
|
||||
},
|
||||
ModalSubBodyText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "BodyText2");
|
||||
},
|
||||
ModalImage() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "Image");
|
||||
},
|
||||
ModalCloseButtonText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.$refs[this.ModalName].openModal();
|
||||
},
|
||||
|
||||
footerButtonClick() {
|
||||
this.$refs[this.ModalName].closeModal();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
modal,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -8,7 +8,7 @@ import { useMainStore } from "@/store";
|
|||
import baseMixin from "@/mixins/base-mixin";
|
||||
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { fetchCmsContentForPage, setupModalLinks } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper.js";
|
||||
|
||||
jest.mock("@/helpers/damage-helper", () => ({
|
||||
|
|
@ -20,9 +20,10 @@ jest.mock("@/helpers/layout-helper.js", () => ({
|
|||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock fetchCmsContentForPage
|
||||
// Mock fetchCmsContentForPage, setupModalLinks
|
||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||
fetchCmsContentForPage: jest.fn(),
|
||||
setupModalLinks: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("coverage-statement.vue...", () => {
|
||||
|
|
|
|||
|
|
@ -1,48 +1,49 @@
|
|||
<template>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" >
|
||||
<div class="page-container-grouped-styles">
|
||||
<div class="fade-on-route-transition position-relative">
|
||||
<siteHeader cmsWidgetName="SiteHeaderWidget" />
|
||||
<div class="select-car">
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row px-3">
|
||||
<div class="col">
|
||||
<div class="select-car-form rounded">
|
||||
<textBlock
|
||||
cmsWidgetName="verifyingCoverageStatement"
|
||||
typeStyle="h5"
|
||||
justifyText="center"
|
||||
class="mt-0 mb-4"
|
||||
id="coverage-statement-text-block"
|
||||
/>
|
||||
<div>
|
||||
<p v-html="continueWithSchedulingBodyText" class="mt-0 small" ></p>
|
||||
</div>
|
||||
<textBlock
|
||||
cmsWidgetName="whatHappensNextCopy"
|
||||
class="mt-4 mb-2 fw-bold"
|
||||
id="coverage-statement-text-block"
|
||||
/>
|
||||
<div>
|
||||
<p class="small" v-html="bodyText" ref="coverageStatementBodyText"></p>
|
||||
</div>
|
||||
<recalModal cmsWidgetName="RecalModal" />
|
||||
<steeringText cmsWidgetName="MASteeringText" ></steeringText>
|
||||
<siteFooter
|
||||
cmsWidgetName="SiteFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@backClicked="navigateBack"
|
||||
@forwardClicked="forwardButtonAction"
|
||||
ref="siteFooter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" >
|
||||
<div class="page-container-grouped-styles">
|
||||
<div class="fade-on-route-transition position-relative">
|
||||
<siteHeader cmsWidgetName="SiteHeaderWidget" />
|
||||
<div class="select-car">
|
||||
<div class="container-fluid pb-2">
|
||||
<div class="row px-3">
|
||||
<div class="col">
|
||||
<div class="select-car-form rounded">
|
||||
<textBlock
|
||||
cmsWidgetName="verifyingCoverageStatement"
|
||||
typeStyle="h5"
|
||||
justifyText="center"
|
||||
class="mt-0 mb-4"
|
||||
id="coverage-statement-text-block"
|
||||
/>
|
||||
<div>
|
||||
<p v-html="continueWithSchedulingBodyText" class="mt-0 small" ></p>
|
||||
</div>
|
||||
<textBlock
|
||||
cmsWidgetName="whatHappensNextCopy"
|
||||
class="mt-4 mb-2 fw-bold"
|
||||
id="coverage-statement-text-block"
|
||||
/>
|
||||
<div>
|
||||
<p class="small" v-html="bodyText" ref="coverageStatementBodyText"></p>
|
||||
</div>
|
||||
|
||||
<steeringText cmsWidgetName="MASteeringText" ></steeringText>
|
||||
<siteFooter
|
||||
cmsWidgetName="SiteFooterWidget"
|
||||
:isForwardActionDisabled="!meta.valid"
|
||||
@backClicked="navigateBack"
|
||||
@forwardClicked="forwardButtonAction"
|
||||
ref="siteFooter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<recalModal ref="RecalModal" cmsWidgetName="RecalModal" />
|
||||
</Form>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
|
|
@ -56,116 +57,119 @@ import textBlock from "@/digital-components/text-block/text-block";
|
|||
import recalModal from '@/layouts/coverage-statement/recal-modal/recal-modal.vue';
|
||||
import steeringText from "@/iss-components/steering-text/steering-text.vue";
|
||||
// Import Supporting Files
|
||||
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||
import { fetchCmsContentForPage, setupModalLinks } from '@/helpers/cms-content-helper';
|
||||
import { settleAllPromises } from '@/helpers/layout-helper';
|
||||
import { getDamageString } from '@/helpers/damage-helper.js';
|
||||
import { useMainStore } from "@/store";
|
||||
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||
|
||||
export default {
|
||||
name: 'coverage-statement',
|
||||
mixins: [baseFormMixin, vehicleQuestionsMixin],
|
||||
components: {
|
||||
siteFooter,
|
||||
siteHeader,
|
||||
siteSubHeader,
|
||||
Form,
|
||||
textBlock,
|
||||
recalModal,
|
||||
steeringText
|
||||
},
|
||||
computed: {
|
||||
bodyText() {
|
||||
if (useMainStore().order.damage.isRepair) {
|
||||
return this.unverifiedNonADASRepairBodyText;
|
||||
}
|
||||
else {
|
||||
let parts = useMainStore().order.lineItems.glassParts;
|
||||
name: 'coverage-statement',
|
||||
mixins: [baseFormMixin, vehicleQuestionsMixin],
|
||||
components: {
|
||||
siteFooter,
|
||||
siteHeader,
|
||||
siteSubHeader,
|
||||
Form,
|
||||
textBlock,
|
||||
recalModal,
|
||||
steeringText
|
||||
},
|
||||
mounted() {
|
||||
setupModalLinks(this);
|
||||
},
|
||||
computed: {
|
||||
bodyText() {
|
||||
if (useMainStore().order.damage.isRepair) {
|
||||
return this.unverifiedNonADASRepairBodyText;
|
||||
}
|
||||
else {
|
||||
let parts = useMainStore().order.lineItems.glassParts;
|
||||
|
||||
// if ADAS, display ADASNextSteps
|
||||
if (parts.filter(part => part.requiresRecalibration).length > 0) {
|
||||
return this.unverifiedADASNextStepsBodyText;
|
||||
}
|
||||
// if non-ADAS, display NonADASNextSteps
|
||||
else {
|
||||
return this.unverifiedNonADASNextStepsBodyText;
|
||||
}
|
||||
}
|
||||
},
|
||||
continueWithSchedulingBodyText() {
|
||||
return this.getCmsContent("continueWithSchedulingCopy", "BodyText");
|
||||
},
|
||||
unverifiedADASNextStepsBodyText() {
|
||||
return this.getCmsContent("UnverifiedADASNextStepsWidget", "BodyText").replaceAll("{custom:damage}", this.damageText);
|
||||
},
|
||||
unverifiedNonADASNextStepsBodyText() {
|
||||
return this.getCmsContent("UnverifiedNonADASNextStepsWidget", "BodyText").replaceAll("{custom:damage}", this.damageText);
|
||||
},
|
||||
unverifiedNonADASRepairBodyText() {
|
||||
return this.getCmsContent("UnverifiedNonADASRepairWidget", "BodyText");
|
||||
},
|
||||
damageText() {
|
||||
var damageString = getDamageString();
|
||||
return damageString == "match" ? "" : damageString;
|
||||
},
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
||||
// if ADAS, display ADASNextSteps
|
||||
if (parts.filter(part => part.requiresRecalibration).length > 0) {
|
||||
return this.unverifiedADASNextStepsBodyText;
|
||||
}
|
||||
// if non-ADAS, display NonADASNextSteps
|
||||
else {
|
||||
return this.unverifiedNonADASNextStepsBodyText;
|
||||
}
|
||||
}
|
||||
},
|
||||
continueWithSchedulingBodyText() {
|
||||
return this.getCmsContent("continueWithSchedulingCopy", "BodyText");
|
||||
},
|
||||
unverifiedADASNextStepsBodyText() {
|
||||
return this.getCmsContent("UnverifiedADASNextStepsWidget", "BodyText").replaceAll("{custom:damage}", this.damageText);
|
||||
},
|
||||
unverifiedNonADASNextStepsBodyText() {
|
||||
return this.getCmsContent("UnverifiedNonADASNextStepsWidget", "BodyText").replaceAll("{custom:damage}", this.damageText);
|
||||
},
|
||||
unverifiedNonADASRepairBodyText() {
|
||||
return this.getCmsContent("UnverifiedNonADASRepairWidget", "BodyText");
|
||||
},
|
||||
damageText() {
|
||||
var damageString = getDamageString();
|
||||
return damageString == "match" ? "" : damageString;
|
||||
},
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
||||
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: 'cmsContent',
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
];
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: 'cmsContent',
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
if (useMainStore().order.vehicle.vin) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
next((vm) => {
|
||||
vm.setCmsContent(resultMap.cmsContent);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
if (useMainStore().order.vehicle.vin) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
async forwardButtonAction() {
|
||||
return this.navigateForward();
|
||||
},
|
||||
async forwardButtonAction() {
|
||||
return this.navigateForward();
|
||||
},
|
||||
|
||||
navigateForward() {
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_FORWARD_COVERAGE_STATEMENT,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
navigateForward() {
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_FORWARD_COVERAGE_STATEMENT,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
ol {
|
||||
margin-left: -1rem;
|
||||
li {
|
||||
margin-bottom: .5rem;
|
||||
line-height: 1.5rem;
|
||||
a {
|
||||
line-height: 1.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
margin-left: -1rem;
|
||||
li {
|
||||
margin-bottom: .5rem;
|
||||
line-height: 1.5rem;
|
||||
a {
|
||||
line-height: 1.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#coverage-statement-text-block {
|
||||
color: #000000;
|
||||
color: $black;
|
||||
}
|
||||
p strong {
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import { shallowMount, mount } from "@vue/test-utils";
|
||||
import recalModal from "@/layouts/coverage-statement/recal-modal/recal-modal.vue";
|
||||
|
||||
describe("modal.vue", () => {
|
||||
it("Should display header text when HeaderText is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(recalModal, {
|
||||
const wrapper = mount(recalModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
|
|
@ -16,7 +16,7 @@ describe("modal.vue", () => {
|
|||
|
||||
it("Should display subheader text when SubheaderText is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(recalModal, {
|
||||
const wrapper = mount(recalModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
|
|
@ -28,7 +28,7 @@ describe("modal.vue", () => {
|
|||
|
||||
it("Should insert image url when Image is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(recalModal, {
|
||||
const wrapper = mount(recalModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
|
|
@ -40,7 +40,7 @@ describe("modal.vue", () => {
|
|||
|
||||
it("Should display body text when BodyText is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(recalModal, {
|
||||
const wrapper = mount(recalModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
|
|
|
|||
|
|
@ -1,55 +1,45 @@
|
|||
<template>
|
||||
<!-- Modal -->
|
||||
<div
|
||||
class="modal fade modal-component"
|
||||
v-on="{ 'hidden.bs.modal': resetButtonStyle }"
|
||||
:id="cmsWidgetName"
|
||||
tabindex="-1"
|
||||
aria-labelledby="ModalComponentLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body ps-4 pe-4 pt-0 pb-5">
|
||||
<h5 class="mb-4 text-center" v-html="ModalHeadline"></h5>
|
||||
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
|
||||
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p>
|
||||
<p class="mb-0 small" v-html="ModalBodyText"></p>
|
||||
<p
|
||||
class="my-4 caption modal-sub-body"
|
||||
v-if="ModalSubBodyText"
|
||||
v-html="ModalSubBodyText"></p>
|
||||
</div>
|
||||
<div class="modal-footer px-5 py-4">
|
||||
<buttonMain
|
||||
isPrimary
|
||||
class="w-100"
|
||||
ref="buttonMain"
|
||||
suppressLoader
|
||||
:buttonText="ModalCloseButtonText"
|
||||
@click-event="buttonClick"
|
||||
data-bs-dismiss="modal" />
|
||||
</div>
|
||||
</div>
|
||||
<modal
|
||||
:ref="ModalName"
|
||||
:modalId="ModalName"
|
||||
:footerButtonText="ModalCloseButtonText"
|
||||
@footer-button-event="closeModal"
|
||||
>
|
||||
<div class="recal-modal-body ps-4 pe-4 pt-0 pb-5">
|
||||
<h5 class="mb-4 text-center" v-html="ModalHeadline"></h5>
|
||||
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
|
||||
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p>
|
||||
<p class="mb-0 small" v-html="ModalBodyText"></p>
|
||||
<p
|
||||
class="my-4 caption modal-sub-body"
|
||||
v-if="ModalSubBodyText"
|
||||
v-html="ModalSubBodyText">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonMain from "@/ux-components/button-main/button-main";
|
||||
import modal from "@/digital-components/modal/modal";
|
||||
|
||||
export default {
|
||||
name: "modal",
|
||||
name: "recal-modal",
|
||||
props: {
|
||||
cmsWidgetName: String,
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.$refs[this.ModalName].openModal();
|
||||
},
|
||||
closeModal() {
|
||||
this.$refs[this.ModalName].closeModal();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
ModalName() {
|
||||
return this.cmsWidgetName;
|
||||
},
|
||||
ModalHeadline() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
|
||||
},
|
||||
|
|
@ -69,77 +59,30 @@ export default {
|
|||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetButtonStyle() {
|
||||
this.$refs.buttonMain.resetButtonStyle();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonMain,
|
||||
modal,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
h5,
|
||||
strong,
|
||||
.subheader-text {
|
||||
color: $black;
|
||||
}
|
||||
.modal-header {
|
||||
border-bottom: none;
|
||||
.btn-close {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.6874 1.82179L9.50889 8L15.6874 14.1782C16.1042 14.595 16.1042 15.2707 15.6874 15.6875C15.4843 15.8907 15.2146 16 14.9328 16C14.6514 16 14.3818 15.8906 14.1787 15.6875L8.00017 9.50934L1.82171 15.6875C1.6182 15.8907 1.34876 16 1.06708 16C0.785733 16 0.516056 15.8906 0.312965 15.6875C-0.10429 15.2706 -0.10429 14.5952 0.312797 14.1784L6.03276 8.45867L6.49146 8L0.312945 1.82177C-0.10429 1.40483 -0.10429 0.729418 0.312797 0.31262C0.728936 -0.104145 1.40489 -0.104051 1.82185 0.31262L7.54152 6.03202L8.00017 6.49065L14.1786 0.312472C14.5955 -0.104107 15.2707 -0.104201 15.6874 0.312452C16.1042 0.729289 16.1042 1.40496 15.6874 1.82179Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
|
||||
opacity: 1;
|
||||
|
||||
.recal-modal-body {
|
||||
.modal-sub-body {
|
||||
color: $gray-600;
|
||||
}
|
||||
}
|
||||
&.modal-component {
|
||||
.modal-dialog {
|
||||
max-width: 576px;
|
||||
margin: 0 auto;
|
||||
.modal-content {
|
||||
margin: 0 auto;
|
||||
box-shadow: 0px 16px 48px -16px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 1.5rem 1.5rem 0 0;
|
||||
.modal-body {
|
||||
.modal-sub-body {
|
||||
color: $gray-600;
|
||||
}
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
p {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.modal-dialog-centered {
|
||||
align-items: flex-end;
|
||||
min-height: 100%;
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
p {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.modal-footer {
|
||||
border-top: none;
|
||||
background-color: $gray-100;
|
||||
box-shadow: 0px -1px 0px rgba(179, 180, 181, 0.3);
|
||||
button {
|
||||
margin: 0;
|
||||
}
|
||||
.subheader-text {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
body {
|
||||
.modal-backdrop {
|
||||
height: 100%;
|
||||
&.show {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
|
@ -146,11 +146,11 @@
|
|||
break;
|
||||
|
||||
case "returnurl":
|
||||
this.mainStore.iisConfig.returnURL = value;
|
||||
this.mainStore.issConfig.returnURL = value;
|
||||
break;
|
||||
|
||||
case "returnurl2":
|
||||
this.mainStore.iisConfig.returnURL2 = value;
|
||||
this.mainStore.issConfig.returnURL2 = value;
|
||||
break;
|
||||
|
||||
// Not stored
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ function setupMocks({
|
|||
],
|
||||
route: {
|
||||
query: {
|
||||
fmgPage: "part-questions",
|
||||
issPage: "part-questions",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
v-html="ProviderPreferenceBodyText"
|
||||
class="mt-0 body-text"
|
||||
></div>
|
||||
<shoppreferenceModal cmsWidgetName="ShopPreferenceDrawer" />
|
||||
|
||||
<buttonQuestion
|
||||
cmsWidgetName="ServiceLocationQuestion"
|
||||
:questionText="questionText"
|
||||
|
|
@ -38,15 +38,19 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<contentGroupModal cmsWidgetName="ShopPreferenceDrawer"
|
||||
ref="ShopPreferenceDrawer"
|
||||
/>
|
||||
</Form>
|
||||
</template>
|
||||
<script>
|
||||
// Import Supporting Files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { fetchCmsContentForPage, setupModalLinks } from '@/helpers/cms-content-helper';
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import buttonQuestion from "@/digital-components/button-question/button-question";
|
||||
import contentGroupModal from "@/iss-components/content-group-modal/content-group-modal"
|
||||
|
||||
// Import Component
|
||||
import baseFormMixin from "@/mixins/base-form-mixin";
|
||||
|
|
@ -54,7 +58,6 @@ import { Form,defineRule } from "vee-validate";
|
|||
import siteFooter from "@/iss-components/site-footer/site-footer.vue";
|
||||
import siteHeader from "@/iss-components/site-header/site-header.vue";
|
||||
import siteSubHeader from "@/iss-components/site-sub-header/site-sub-header.vue";
|
||||
import shoppreferenceModal from '@/layouts/provider-preference/shoppreference-modal/shoppreference-modal.vue';
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("questions-required", required(errorMessages.OPTION_REQUIRED));
|
||||
|
|
@ -67,8 +70,8 @@ export default {
|
|||
siteHeader,
|
||||
siteSubHeader,
|
||||
Form,
|
||||
shoppreferenceModal,
|
||||
buttonQuestion
|
||||
buttonQuestion,
|
||||
contentGroupModal
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -124,6 +127,9 @@ export default {
|
|||
},
|
||||
resetDependentState() {},
|
||||
},
|
||||
mounted() {
|
||||
setupModalLinks(this);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import shoppreferenceModal from '@/layouts/provider-preference/shoppreference-modal/shoppreference-modal.vue';
|
||||
|
||||
describe("modal.vue", () => {
|
||||
it("Should display header text when HeaderText is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(shoppreferenceModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"]));
|
||||
});
|
||||
|
||||
it("Should display body text when BodyText is defined in the CMS", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(shoppreferenceModal, {
|
||||
mixins: [mockMixin],
|
||||
props: {
|
||||
cmsWidgetName: "test",
|
||||
},
|
||||
attachTo: document.body,
|
||||
});
|
||||
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"]));
|
||||
});
|
||||
});
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
return mockCmsContent[cmsFieldName];
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const mockCmsContent = {
|
||||
HeaderText: "Sample header text here.",
|
||||
BodyText: "Sample body text here.",
|
||||
FooterText: "Sample footer text here.",
|
||||
};
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
<template>
|
||||
<!-- Modal -->
|
||||
<div
|
||||
class="modal fade modal-component"
|
||||
v-on="{ 'hidden.bs.modal': resetButtonStyle }"
|
||||
:id="cmsWidgetName"
|
||||
tabindex="-1"
|
||||
aria-labelledby="ModalComponentLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body ps-4 pe-4 pt-4 pb-4">
|
||||
<h5 class="mb-2 text-center header-text" v-html="ModalHeadline"></h5>
|
||||
<p class="mb-0 small body-text" v-html="ModalBodyText"></p>
|
||||
</div>
|
||||
<div class="modal-footer px-5 py-4">
|
||||
<buttonMain
|
||||
isPrimary
|
||||
class="w-100"
|
||||
ref="buttonMain"
|
||||
suppressLoader
|
||||
:buttonText="ModalCloseButtonText"
|
||||
@click-event="buttonClick"
|
||||
data-bs-dismiss="modal" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonMain from "@/ux-components/button-main/button-main";
|
||||
|
||||
export default {
|
||||
name: "modal",
|
||||
props: {
|
||||
cmsWidgetName: String,
|
||||
},
|
||||
computed: {
|
||||
ModalHeadline() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
|
||||
},
|
||||
ModalBodyText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "BodyText");
|
||||
},
|
||||
ModalCloseButtonText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "FooterText");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetButtonStyle() {
|
||||
this.$refs.buttonMain.resetButtonStyle();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonMain,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
h5,
|
||||
strong,
|
||||
.header-text{
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 26px;
|
||||
}
|
||||
.body-text{
|
||||
color:#525656;
|
||||
font-size: 16px;
|
||||
line-height: 26px;
|
||||
}
|
||||
.modal-header {
|
||||
border-bottom: none;
|
||||
.btn-close {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.6874 1.82179L9.50889 8L15.6874 14.1782C16.1042 14.595 16.1042 15.2707 15.6874 15.6875C15.4843 15.8907 15.2146 16 14.9328 16C14.6514 16 14.3818 15.8906 14.1787 15.6875L8.00017 9.50934L1.82171 15.6875C1.6182 15.8907 1.34876 16 1.06708 16C0.785733 16 0.516056 15.8906 0.312965 15.6875C-0.10429 15.2706 -0.10429 14.5952 0.312797 14.1784L6.03276 8.45867L6.49146 8L0.312945 1.82177C-0.10429 1.40483 -0.10429 0.729418 0.312797 0.31262C0.728936 -0.104145 1.40489 -0.104051 1.82185 0.31262L7.54152 6.03202L8.00017 6.49065L14.1786 0.312472C14.5955 -0.104107 15.2707 -0.104201 15.6874 0.312452C16.1042 0.729289 16.1042 1.40496 15.6874 1.82179Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&.modal-component {
|
||||
.modal-dialog {
|
||||
max-width: 576px;
|
||||
margin: 0 auto;
|
||||
.modal-content {
|
||||
margin: 0 auto;
|
||||
box-shadow: 0px 16px 48px -16px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 1.5rem 1.5rem 0 0;
|
||||
.modal-body {
|
||||
.modal-sub-body {
|
||||
color: $gray-600;
|
||||
}
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
p {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.modal-dialog-centered {
|
||||
align-items: flex-end;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
.modal-footer {
|
||||
border-top: none;
|
||||
background-color: $gray-100;
|
||||
box-shadow: 0px -1px 0px rgba(179, 180, 181, 0.3);
|
||||
button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
body {
|
||||
.modal-backdrop {
|
||||
height: 100%;
|
||||
&.show {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -63,4 +63,11 @@ body {
|
|||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-text {
|
||||
display: inline;
|
||||
color: $blue;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ describe("list-button.vue", () => {
|
|||
mockData: {
|
||||
global: {
|
||||
mocks: {
|
||||
$route: { query: { fmgPage: "page-name" } },
|
||||
$route: { query: { issPage: "page-name" } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
},
|
||||
|
|
@ -37,7 +37,7 @@ describe("list-button.vue", () => {
|
|||
mockData: {
|
||||
global: {
|
||||
mocks: {
|
||||
$route: { query: { fmgPage: "page-name" } },
|
||||
$route: { query: { issPage: "page-name" } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
},
|
||||
|
|
@ -64,7 +64,7 @@ describe("list-button.vue", () => {
|
|||
mockData: {
|
||||
global: {
|
||||
mocks: {
|
||||
$route: { query: { fmgPage: "page-name" } },
|
||||
$route: { query: { issPage: "page-name" } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
},
|
||||
|
|
|
|||
188
src/ux-components/modal-button-main/modal-button-main.spec.js
Normal file
188
src/ux-components/modal-button-main/modal-button-main.spec.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import modalButtonMain from "./modal-button-main";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
describe("modal-button-main.vue", () => {
|
||||
it("Should return btn-primary class", () => {
|
||||
// Arrange/Act
|
||||
const wrapper = shallowMount(
|
||||
modalButtonMain,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
isPrimary: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
const button = wrapper.find("button");
|
||||
|
||||
// Assert
|
||||
expect(button.attributes("class")).toContain("btn-primary");
|
||||
});
|
||||
|
||||
it("Should return aria-disabled state", () => {
|
||||
// Arrange/Act
|
||||
const wrapper = shallowMount(
|
||||
modalButtonMain,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
isDisabled: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
const button = wrapper.find("button");
|
||||
|
||||
// Assert
|
||||
expect(button.attributes()["aria-disabled"]).toEqual("true");
|
||||
});
|
||||
|
||||
it("Should return loader color", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(
|
||||
modalButtonMain,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
loaderColor: "blue",
|
||||
loaderEnabled: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
wrapper.vm.clicked();
|
||||
|
||||
await nextTick();
|
||||
|
||||
// Assert
|
||||
const loader = wrapper.find("loader-stub");
|
||||
expect(loader.attributes("class")).toContain("blue");
|
||||
});
|
||||
|
||||
it("Should return loader position", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(
|
||||
modalButtonMain,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
loaderPosition: "right",
|
||||
loaderEnabled: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Act
|
||||
wrapper.vm.clicked();
|
||||
await nextTick();
|
||||
|
||||
// Assert
|
||||
const loader = wrapper.find("loader-stub");
|
||||
expect(loader.attributes("class")).toContain("right");
|
||||
});
|
||||
|
||||
it("Should set 'isLoaderDisplayed' to false when calling 'removeLoader'", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(
|
||||
modalButtonMain,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
loaderPosition: "right",
|
||||
loaderEnabled: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
wrapper.setData({
|
||||
isLoaderDisplayed: true,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.removeLoader();
|
||||
await nextTick();
|
||||
|
||||
// Assert
|
||||
const loader = wrapper.find("loader-stub");
|
||||
expect(wrapper.vm.isLoaderDisplayed).toBe(false);
|
||||
});
|
||||
|
||||
it("Should set 'isLoaderDisplayed' to false when calling 'resetButtonStyle'", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(
|
||||
modalButtonMain,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
loaderPosition: "right",
|
||||
loaderEnabled: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
wrapper.setData({
|
||||
isLoaderDisplayed: true,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.resetButtonStyle();
|
||||
await nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.isLoaderDisplayed).toBe(false);
|
||||
});
|
||||
|
||||
it("Should emit 'click-event' event when clicking if the button is enabled", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(
|
||||
modalButtonMain,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
loaderPosition: "right",
|
||||
loaderEnabled: true,
|
||||
isDisabled: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const buttonElement = wrapper.find("button");
|
||||
|
||||
// Act
|
||||
buttonElement.trigger("click");
|
||||
|
||||
await nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted("click-event")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("Should not emit 'click-event' event when clicking if the button is disabled", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(
|
||||
modalButtonMain,
|
||||
setupMocks({
|
||||
propsData: {
|
||||
loaderPosition: "right",
|
||||
loaderEnabled: true,
|
||||
isDisabled: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const buttonElement = wrapper.find("button");
|
||||
|
||||
// Act
|
||||
buttonElement.trigger("click");
|
||||
|
||||
await nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted("click-event")).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks(mountOptionsMockData = {}) {
|
||||
const defaultMountOptions = { route: { query: { issPage: "page-name" } } };
|
||||
const baseMountOptions = getMountOptions(
|
||||
Object.assign(defaultMountOptions, mountOptionsMockData)
|
||||
);
|
||||
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
|
||||
|
||||
return allMountOptions;
|
||||
}
|
||||
153
src/ux-components/modal-button-main/modal-button-main.vue
Normal file
153
src/ux-components/modal-button-main/modal-button-main.vue
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<button
|
||||
type="button"
|
||||
:aria-disabled="isDisabled"
|
||||
class="btn d-flex align-items-center justify-content-center py-3 px-4 delay"
|
||||
:class="[
|
||||
isPrimary ? 'btn-primary' : 'btn-secondary',
|
||||
isFloat ? 'float-end' : '',
|
||||
isLoaderDisplayed ? 'has-loader' : '',
|
||||
]"
|
||||
@click="clicked">
|
||||
<span class="m-0">{{ this.buttonText }}</span>
|
||||
<loader
|
||||
class="ms-2"
|
||||
v-if="isLoaderDisplayed && !suppressLoader"
|
||||
v-bind:class="[this.loaderColor, this.loaderPosition]" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import loader from "@/ux-components/loader/loader";
|
||||
|
||||
export default {
|
||||
name: "modalButtonMain",
|
||||
props: {
|
||||
isPrimary: Boolean,
|
||||
buttonText: String,
|
||||
isDisabled: Boolean,
|
||||
loaderColor: String,
|
||||
loaderPosition: String,
|
||||
isFloat: Boolean,
|
||||
suppressLoader: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoaderDisplayed: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
removeLoader() {
|
||||
this.isLoaderDisplayed = false;
|
||||
},
|
||||
clicked() {
|
||||
this.pushEventToGA(
|
||||
this.$route.query[this.queryStrings.ISS_PAGE],
|
||||
this.GaActions.CLICKED,
|
||||
this.buttonText,
|
||||
true
|
||||
);
|
||||
if (!this.isDisabled) {
|
||||
this.isLoaderDisplayed = true;
|
||||
this.$emit("click-event");
|
||||
}
|
||||
},
|
||||
resetButtonStyle() {
|
||||
this.isLoaderDisplayed = false;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
loader,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.btn {
|
||||
&.btn-primary {
|
||||
position: relative;
|
||||
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
|
||||
border: none;
|
||||
border-radius: $border-radius-lg;
|
||||
color: $white;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
@media (hover: hover) {
|
||||
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
|
||||
}
|
||||
&:focus, // Mouse, touch, stylus focus
|
||||
&:focus-visible {
|
||||
// Keyboard focus for accessibility
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
|
||||
color: $white;
|
||||
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
|
||||
}
|
||||
&:disabled {
|
||||
background: $gray-200 !important;
|
||||
background: linear-gradient(270deg, $gray-200 0%, $gray-200 100%) !important;
|
||||
color: $gray-600 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: none;
|
||||
border-radius: $border-radius-lg;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
&.has-loader {
|
||||
color: $white;
|
||||
background: $blue-700;
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
|
||||
pointer-events: none;
|
||||
}
|
||||
&.delay {
|
||||
// fixes flicker while transitioning between states
|
||||
transition: background 0s 0s ease-in-out;
|
||||
}
|
||||
}
|
||||
&.btn-secondary {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border: 1px solid $blue;
|
||||
border-radius: $border-radius-lg;
|
||||
color: $blue;
|
||||
font-weight: 500;
|
||||
transition: all 150ms linear;
|
||||
height: 3rem;
|
||||
&:hover {
|
||||
color: $white;
|
||||
@include blue-gradient;
|
||||
}
|
||||
&:focus, // Mouse, touch, stylus focus
|
||||
&:focus-visible {
|
||||
// Keyboard focus for accessibility
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue-700;
|
||||
color: $white;
|
||||
@include blue-gradient;
|
||||
}
|
||||
&:disabled {
|
||||
background: transparent;
|
||||
color: $gray-550 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: 1px solid $gray-550;
|
||||
border-radius: $border-radius-lg;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
&.has-loader {
|
||||
color: $white;
|
||||
@include blue-gradient;
|
||||
pointer-events: none;
|
||||
}
|
||||
&.delay {
|
||||
// fixes flicker while transitioning between states
|
||||
transition: background 0s 0s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in a new issue