Skip to content

feat: message hover and click behavior and modal #2352

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/ui/buttons/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ export function normalizeButtonMessage(
}
}

if (offer) {
if (typeof offer !== "undefined") {
if (!Array.isArray(offer)) {
throw new TypeError(
`Expected message.offer to be an array of strings, got: ${String(
Expand All @@ -776,15 +776,18 @@ export function normalizeButtonMessage(
}
}

if (color && !values(MESSAGE_COLOR).includes(color)) {
if (typeof color !== "undefined" && !values(MESSAGE_COLOR).includes(color)) {
throw new Error(`Invalid color: ${color}`);
}

if (position && !values(MESSAGE_POSITION).includes(position)) {
if (
typeof position !== "undefined" &&
!values(MESSAGE_POSITION).includes(position)
) {
throw new Error(`Invalid position: ${position}`);
}

if (align && !values(MESSAGE_ALIGN).includes(align)) {
if (typeof align !== "undefined" && !values(MESSAGE_ALIGN).includes(align)) {
throw new Error(`Invalid align: ${align}`);
}

Expand Down
44 changes: 44 additions & 0 deletions src/zoid/buttons/component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
getRenderedButtons,
getButtonSize,
getButtonExperiments,
getModal,
} from "./util";

export type ButtonsComponent = ZoidComponent<ButtonProps>;
Expand Down Expand Up @@ -681,6 +682,49 @@
},
},

onMessageHover: {
type: "function",
required: false,
value: () => {
return () => {
// lazy loads the modal, to be memoized and executed onMessageClick
getModal();
};
},
},

onMessageClick: {
type: "function",
required: false,
value: ({ props }) => {
return async ({ offerType, messageType }) => {

Check failure on line 700 in src/zoid/buttons/component.jsx

View workflow job for this annotation

GitHub Actions / main

'messageType' is defined but never used
const { message, clientID, merchantID, currency, buttonSessionID } =
props;
const amount = message?.amount || undefined;

const modalInstance = await getModal(clientID, merchantID);
modalInstance.show({
amount,
offer: offerType?.join(",") || undefined,
currency,
});

getLogger()
.info("button_message_clicked")
.track({
[FPTI_KEY.EVENT_NAME]: "message_click",
// [FPTI_KEY.BUTTON_MESSAGE_OFFER_TYPE]: offerType,
// [FPTI_KEY.BUTTON_MESSAGE_TYPE]: messageType,
// [FPTI_KEY.BUTTON_MESSAGE_POSITION]: message.position,
// [FPTI_KEY.BUTTON_MESSAGE_ALIGN]: message.align,
// [FPTI_KEY.BUTTON_MESSAGE_COLOR]: message.color,
// [FPTI_KEY.AMOUNT]: amount,
[FPTI_KEY.BUTTON_SESSION_UID]: buttonSessionID,
});
};
},
},

onShippingAddressChange: {
type: "function",
required: false,
Expand Down
61 changes: 61 additions & 0 deletions src/zoid/buttons/util.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable compat/compat */

Check failure on line 1 in src/zoid/buttons/util.js

View workflow job for this annotation

GitHub Actions / main

'compat/compat' rule is disabled but never reported

Check failure on line 1 in src/zoid/buttons/util.js

View workflow job for this annotation

GitHub Actions / main

Requires 'eslint-enable' directive for 'compat/compat'
/* eslint-disable promise/no-native */

Check failure on line 2 in src/zoid/buttons/util.js

View workflow job for this annotation

GitHub Actions / main

Requires 'eslint-enable' directive for 'promise/no-native'
/* @flow */
import {
supportsPopups as userAgentSupportsPopups,
Expand All @@ -13,6 +15,7 @@
getElement,
isStandAlone,
once,
memoize,
} from "@krakenjs/belter/src";
import { FUNDING } from "@paypal/sdk-constants/src";
import {
Expand All @@ -22,6 +25,8 @@
getFundingEligibility,
getPlatform,
getComponents,
getEnv,
getNamespace,
} from "@paypal/sdk-client/src";
import { getRefinedFundingEligibility } from "@paypal/funding-components/src";

Expand Down Expand Up @@ -357,3 +362,59 @@
}
}
}

export const getModal: (
clientID: string,
merchantID: $ReadOnlyArray<string> | void
) => Object = memoize((clientID, merchantID) => {
const namespace = getNamespace();

if (!window[namespace]?.MessagesModal) {
const modalBundleUrl = () => {
let envPiece;
switch (getEnv()) {
case "local":
case "test":
case "stage":
envPiece = "stage";
break;
case "sandbox":
envPiece = "sandbox";
break;
case "production":
default:
envPiece = "js";
}
return `https://www.paypalobjects.com/upstream/bizcomponents/${envPiece}/modal.js`;
};

try {
// eslint-disable-next-line no-restricted-globals
return new Promise(() => {
const script = document.createElement("script");
script.src = modalBundleUrl();
script.setAttribute("data-pp-namespace", namespace);
script.addEventListener("error", (err: Event) => {
throw err;
});
document.body?.appendChild(script);
script.addEventListener("load", () => {
document.body?.removeChild(script);
});
});
} catch (err) {
getLogger()
.info("button_message_modal_fetch_error")
.track({
err: err.message || "BUTTON_MESSAGE_MODAL_FETCH_ERROR",
details: err.details,
stack: JSON.stringify(err.stack || err),
});
}
}
const modal = window[namespace].MessagesModal;
return modal({
account: `client-id:${clientID}`,
merchantId: merchantID?.join(",") || undefined,
});
});
1 change: 1 addition & 0 deletions test/integration/tests/button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "./error";
import "./drivers";
import "./frame";
import "./size";
import "./message";
import "./multiple";
import "./layout";
import "./style";
Expand Down
81 changes: 81 additions & 0 deletions test/integration/tests/button/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,4 +378,85 @@
});
});
});

describe("modal", () => {
const messageMarkup = `
<div className=".message__container locale__en" role="button">
<div className="message__content" >
<div className="message__logo-container">
<div className="message__logo--svg"></div>
</div>
<div className=".message__messaging text__content--black">
<div className="message__headline">
<span style="font-size: 16px">Paypal Pay Later</span>
</div>
<p className="message__disclaimer">
<span></span>
</p>
</div>
</div>
</div>`;

it("should load modal on message hover when window.paypal.MessagesModal is not present", async (done) => {

Check failure on line 400 in test/integration/tests/button/message.js

View workflow job for this annotation

GitHub Actions / main

Async arrow function has no 'await' expression
window.paypal
.Buttons({
message: {},
messageMarkup,
test: {
onRender() {
const message = document.querySelector(".message__container");
message?.focus();
setTimeout(() => assert.ok(window.paypal.MessagesModal), 1000);
done();
},
},
})
.render("#testContainer");
});
it.skip("should utilize existing MessagesModal on message hover when window.paypal.MessagesModal is present", () => {});

Check failure on line 416 in test/integration/tests/button/message.js

View workflow job for this annotation

GitHub Actions / main

Unexpected empty arrow function
it("should open modal on message click", (done) => {
window.paypal.Buttons({
message: {},
messageMarkup,
test: {
onRender() {
const message = document.querySelector(".message__container");
message?.focus();
message?.click();

const modalWrapper = document.querySelector(".modal-wrapper");
assert.ok(modalWrapper);
done();
},
},
});
});
it("should show passed-in amount in modal's pay in 4 view", (done) => {
window.paypal
.Buttons({
message: {
amount: 100,
},
messageMarkup,
test: {
onRender() {
const message = document.querySelector(".message__container");
message?.focus();

const payIn4Button =
document.querySelector(".content__col")?.childNodes[1];
if (payIn4Button instanceof HTMLElement) {
payIn4Button.click();
}

const payIn4Amount =
document.querySelector("#donut__payment__1")?.innerHTML;
assert.equal(payIn4Amount, "$25.00");
done();
},
},
})
.render("#testContainer");
});
});
});
Loading