Merge branch 'develop' into CASH-631

This commit is contained in:
maguire-arman 2025-05-29 15:40:41 -04:00
commit 821108e755
9 changed files with 263 additions and 16 deletions

View file

@ -8,6 +8,7 @@
<funnelFooter />
<loadingModal :showLoader="shouldShowLoader" :showTextCarousel="shouldShowTextCarousel" />
<salesforceWebchat />
<sierra-webchat />
</template>
<script>
@ -16,6 +17,7 @@ import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { showFmgLoadingModal } from "@/helpers/loading-modal-helper";
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer.vue";
import salesforceWebchat from "./digital-components/salesforce-webchat/salesforce-webchat.vue";
import sierraWebchat from "./digital-components/sierra-webchat/sierra-webchat.vue";
export default {
name: "app",
@ -38,6 +40,7 @@ export default {
loadingModal,
funnelFooter,
salesforceWebchat,
sierraWebchat,
},
mounted() {
showFmgLoadingModal(true);

View file

@ -0,0 +1,84 @@
import { shallowMount } from "@vue/test-utils";
import SierraWebchat from "./sierra-webchat.vue";
describe("sierraWebchat.vue", () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(SierraWebchat);
// Mock global window properties
window.sierraChat = { openChatModal: jest.fn(), closeChatModal: jest.fn() };
window.sierra = undefined;
window.SierraChat = undefined;
window.embedded_svc = {
bootstrapEmbeddedService: jest.fn(),
liveAgentAPI: { startChat: jest.fn() },
settings: {},
};
});
afterEach(() => {
wrapper.unmount();
jest.clearAllMocks();
delete window.sierraChat;
delete window.embedded_svc;
});
it("should mount the component", () => {
expect(wrapper.exists()).toBe(true);
});
it("calls openSierraChatModal when launchSierraChat is called and script is loaded", () => {
const spy = jest.spyOn(wrapper.vm, "openSierraChatModal");
// Simulate script already loaded
document.body.appendChild(document.createElement("script")).id = "sierra-chat-embed";
wrapper.vm.launchSierraChat();
expect(spy).toHaveBeenCalled();
document.getElementById("sierra-chat-embed").remove();
});
it("calls closeSierraChat when invoked", () => {
wrapper.vm.closeSierraChat();
expect(window.sierraChat.closeChatModal).toHaveBeenCalled();
});
it("dispatches sierra-chat-closed on handleSierraOnClose", () => {
const eventSpy = jest.spyOn(window, "dispatchEvent");
wrapper.vm.createSierraConfig().onClose();
expect(eventSpy).toHaveBeenCalledWith(
expect.objectContaining({ type: "sierra-chat-closed" })
);
});
it("dispatches sierra-chat-transfer on handleSierraOnTransfer", () => {
const eventSpy = jest.spyOn(window, "dispatchEvent");
const transfer = {
data: { first_name: "A", last_name: "B", email: "a@b.com", chat_summary: "summary" },
};
wrapper.vm.createSierraConfig().onTransfer(transfer);
expect(eventSpy).toHaveBeenCalledWith(
expect.objectContaining({ type: "sierra-chat-transfer" })
);
});
it("handles sierra-chat-transfer event and starts Salesforce chat with prepopulated fields", () => {
const transfer = {
data: {
first_name: "A",
last_name: "B",
email: "a@b.com",
chat_summary: "summary",
},
};
const event = new CustomEvent("sierra-chat-transfer", { detail: transfer });
window.dispatchEvent(event);
expect(window.sierraChat.closeChatModal).toHaveBeenCalled();
expect(window.embedded_svc.settings.prepopulatedPrechatFields).toEqual({
FirstName: "A",
LastName: "B",
Email: "a@b.com",
Subject: "summary",
});
expect(window.embedded_svc.liveAgentAPI.startChat).toHaveBeenCalled();
});
});

View file

@ -0,0 +1,115 @@
<template>
<div ref="sierraContainer"></div>
</template>
<script>
export default {
name: "sierraWebchat",
methods: {
openSierraChatModal() {
const sierra = window.sierraChat || window.sierra || window.SierraChat;
if (sierra && typeof sierra.openChatModal === "function") {
sierra.openChatModal();
} else {
const launchLink = document.createElement("a");
launchLink.setAttribute("data-sierra-chat", "modal");
launchLink.style.display = "none";
document.body.appendChild(launchLink);
launchLink.click();
document.body.removeChild(launchLink);
}
},
createSierraConfig() {
return {
variables: { application: "funnel" },
display: "corner",
onLoad: () => this.openSierraChatModal(),
onOpen: () => {},
onClose: () => window.dispatchEvent(new CustomEvent("sierra-chat-closed")),
onTransfer: (transfer) =>
window.dispatchEvent(
new CustomEvent("sierra-chat-transfer", { detail: transfer })
),
};
},
launchSierraChat() {
window.sierraConfig = this.createSierraConfig();
// Preload CSS if not already present
if (!document.getElementById("sierra-chat-embed-css")) {
const link = document.createElement("link");
link.rel = "preload";
link.as = "style";
link.href =
"https://sierra.chat/agent/OxY5TbsBlJZsHxIBlJZsmCiQdcXfa2rePO38ZwOlx40/embed-css";
link.id = "sierra-chat-embed-css";
document.head.appendChild(link);
}
// Load script if not loaded, otherwise open modal directly
if (!document.getElementById("sierra-chat-embed")) {
const script = document.createElement("script");
script.type = "module";
script.id = "sierra-chat-embed";
script.src =
"https://sierra.chat/agent/OxY5TbsBlJZsHxIBlJZsmCiQdcXfa2rePO38ZwOlx40/embed";
document.body.appendChild(script);
} else {
this.openSierraChatModal();
}
},
triggerSierraChat() {
this.launchSierraChat();
},
closeSierraChat() {
const sierra = window.sierraChat || window.sierra || window.SierraChat;
if (sierra && typeof sierra.closeChatModal === "function") {
sierra.closeChatModal();
}
},
},
mounted() {
window.addEventListener("launch-sierra-webchat", this.triggerSierraChat);
// Listen for transfer event to close Sierra chat and open Salesforce chat
this._handleSierraTransfer = (event) => {
const sierra = window.sierraChat || window.sierra || window.SierraChat;
if (sierra && typeof sierra.closeChatModal === "function") {
sierra.closeChatModal();
}
// Pass data to Salesforce chat if available
const transfer = event.detail;
if (
window.embedded_svc &&
typeof window.embedded_svc.bootstrapEmbeddedService === "function"
) {
if (
transfer &&
transfer.data &&
transfer.data.first_name &&
transfer.data.last_name &&
transfer.data.email
) {
window.embedded_svc.settings.prepopulatedPrechatFields = {
FirstName: transfer.data.first_name,
LastName: transfer.data.last_name,
Email: transfer.data.email,
Subject: transfer.data.chat_summary, // for now passing in chat_summery in Subject. But this will be changed in future.
};
// Start chat immediately if required fields are present
if (
window.embedded_svc.liveAgentAPI &&
typeof window.embedded_svc.liveAgentAPI.startChat === "function"
) {
window.embedded_svc.liveAgentAPI.startChat();
return;
}
}
window.embedded_svc.bootstrapEmbeddedService();
}
};
window.addEventListener("sierra-chat-transfer", this._handleSierraTransfer);
},
beforeUnmount() {
window.removeEventListener("launch-sierra-webchat", this.triggerSierraChat);
window.removeEventListener("sierra-chat-transfer", this._handleSierraTransfer);
},
};
</script>

View file

@ -44,6 +44,7 @@ import { globalEvents } from "@/constants/events";
import menuModal from "@/fmg-components/funnel-header/menu-modal/menu-modal";
import progressBar from "@/fmg-components/funnel-header/progress-bar/progress-bar";
import { webchatHelper } from "@/helpers/webchat-helper";
import store from "@/store";
// Constants
const ALERT_DURATION = 3000; // millisecond time to display alert before dismissal
@ -53,6 +54,7 @@ export default {
data() {
return {
globalAlertMessages: [],
sierraChatOpen: false,
};
},
props: {
@ -71,6 +73,13 @@ export default {
return this.getCmsContent(this.cmsWidgetName, "LogoImage");
},
shouldShowWebchatButton() {
// Always update experiment flag before computing visibility
this.syncSierraExperimentFlag();
// Show if Sierra experiment is active and chat is not open
if (this.webchatGlobalNonpersistedState.shouldLaunchSierra) {
return !this.sierraChatOpen;
}
// Otherwise, use Salesforce logic
return (
!this.shouldHideWebchatButtonOnPage &&
this.webchatGlobalNonpersistedState.showWebchatButton
@ -91,8 +100,21 @@ export default {
this.globalAlertMessages.push(alertToPush);
},
webchatClicked(event) {
event.preventDefault(); // avoid validation firing
event.preventDefault();
this.launchWebchat();
// Only set sierraChatOpen if Sierra experiment is active
if (this.webchatGlobalNonpersistedState.shouldLaunchSierra) {
this.sierraChatOpen = true;
}
},
syncSierraExperimentFlag() {
const experiments = store.getters.applicationUser.experiments;
const sierraExp = experiments?.find(
(exp) =>
exp.universeName === "CONTENT_FMG_SierraWebchat" &&
exp.settings?.UseSierraChat === "true"
);
this.webchatGlobalNonpersistedState.shouldLaunchSierra = !!sierraExp;
},
},
components: {
@ -122,6 +144,14 @@ export default {
unknownAlertEvent.displayAlert = true;
this.globalAlertMessages.push(unknownAlertEvent);
}
// Listen for Sierra chat close event to re-enable the chat button if needed
this._handleSierraChatClosed = () => {
this.sierraChatOpen = false;
};
window.addEventListener("sierra-chat-closed", this._handleSierraChatClosed);
},
beforeUnmount() {
window.removeEventListener("sierra-chat-closed", this._handleSierraChatClosed);
},
};
</script>

View file

@ -9,7 +9,7 @@ const webchatGlobalNonpersistedState = reactive({
export const webchatHelper = () => {
function launchWebchat() {
if (webchatGlobalNonpersistedState.shouldLaunchSierra) {
// do sierra logic here
window.dispatchEvent(new CustomEvent("launch-sierra-webchat")); // launches the sierra chat
} else {
window.embedded_svc.bootstrapEmbeddedService(); // launches the salesForce prechat form
}

View file

@ -57,7 +57,7 @@
@ForwardClicked="forwardButtonAction" />
<textBlock
class="mb-5"
class="mb-5 disclaimer-block"
cmsWidgetName="DisclaimerCopyWidget"
typeStyle="caption" />
</div>
@ -212,3 +212,10 @@ export default {
},
};
</script>
<style lang="scss" scoped>
.disclaimer-block {
position: relative;
z-index: 2;
}
</style>

View file

@ -1,6 +1,9 @@
<template>
<div class="alert fade show my-2 py-2 border-0 alert-info" role="alert">
<component :is="'script'" src="https://js.afterpay.com/afterpay-1.x.js" async></component>
<component
:is="'script'"
src="https://js.squarecdn.com/square-marketplace.js"
async></component>
<div class="my-4 alert-heading text-center">
<span v-html="afterpayHeaderCopy" />
<span class="afterpay-amount">{{ this.afterpayPrice }}</span>
@ -10,7 +13,7 @@
<a
id="afterpay-learnmore"
href="#"
data-afterpay-modal="en_US-safelite"
data-afterpay-modal="en_US"
data-bind="click:afterpayLearnMore"
class="afterpay-learn-more">
<img :src="infoIcon" alt="Info Icon" class="info-icon" />

View file

@ -1,6 +1,9 @@
<template>
<div class="alert fade show my-2 py-2 border-0 alert-info" role="alert">
<component :is="'script'" src="https://js.afterpay.com/afterpay-1.x.js" async></component>
<component
:is="'script'"
src="https://js.squarecdn.com/square-marketplace.js"
async></component>
<div
class="mx-4 my-0 alert-heading text-center"
@ -17,7 +20,7 @@
<a
id="afterpay-learnmore"
href="#"
data-afterpay-modal="en_US-safelite"
data-afterpay-modal="en_US"
data-bind="click:afterpayLearnMore">
{{ modalCopy }}
</a>

View file

@ -1,17 +1,19 @@
import analyticsMixin from "@/mixins/analytics-mixin";
import { routeData } from "@/router/constants/routes";
import router from "@/router";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper";
export async function bailout(errorPayload, forceRestart = false) {
if (forceRestart) {
await store.dispatch(storeActions.RESET_STATE);
deleteFunnelCookie();
}
analyticsMixin.methods.pushPageErrorToDataLayer(errorPayload);
if (forceRestart) {
router.push({
name: routeData.RESTART.name,
});
} else {
router.push({
name: routeData.ERROR.name,
});
}
router.push({
name: routeData.ERROR.name,
});
}