PayPal Donate button in React: Donate SDK or Checkout SDK integration? - reactjs

The official NPM package #paypal/react-paypal-js: https://www.npmjs.com/package/#paypal/react-paypal-js only supports buttons that require client_id, I have not been able to find anything that simplifies the implementation of Donate button. PayPal provides only this HTML code below on the Donate button documentation. Please let me know if anyone has had a similar experience and how did you manage to implement it properly in React/Gatsby.
<head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Ensures optimal rendering on mobile devices. -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <!-- Optimal Internet Explorer compatibility -->
</head>
<body>
<script src="https://www.paypalobjects.com/donate/sdk/donate-sdk.js" charset="UTF-8"></script>
</body>
<body>
<div id="paypal-donate-button-container"></div>
<script>
PayPal.Donation.Button({
env: 'sandbox',
hosted_button_id: 'YOUR_SANDBOX_HOSTED_BUTTON_ID',
// business: 'YOUR_EMAIL_OR_PAYERID',
image: {
src: 'https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif',
title: 'PayPal - The safer, easier way to pay online!',
alt: 'Donate with PayPal button'
},
onComplete: function (params) {
// Your onComplete handler
},
}).render('#paypal-donate-button-container');
</script>
</body>

There are two ways to use the Donate SDK HTML/JS from react.
Configure the app to load the SDK script at page load time (typically as part of the page's <head>). The JS to render the button can then be run/outputted whenever as part of a component.
Insert the script element into the DOM after page load and once loaded render buttons with a callback function. This can all be placed in a React component's useEffect or similar. The linked example using loadAsync is for the Checkout SDK, but that's easily swapped out for Donate SDK code.
You can use the Donate SDK with a hosted_button_id as you mentioned (created at https://www.paypal.com/buttons (live) or www.sandbox.paypal.com/buttons) -- or alternatively, instead of a hosted button id just pass a business parameter with either the receiving account's PayPal email address or (ideally) its PayPal Merchant ID (since that will never change whereas an email can be removed from the account and no longer point to it)
Later edit: Note that the Donate SDK is separate from simply relabeling a normal PayPal Checkout SDK button with the text "Donate", which can be done per the react-paypal-js storybook example, as another answer mentioned. The issue with this approach is after clicking the button it will look like a standard PayPal checkout, not a donation-specific flow...
Using the actual Donate SDK instead results in a more tailored donor flow where they can select any amount in the large input, and even check an option to make their donation recurring:

I reached out to the React SDK team and they said it actually comes with support for donate out of the box and there is an example in their storybook. Here's the relevant code. If you use this approach you shouldn't need hosted_button_id:
<PayPalButtons
fundingSource="paypal"
style={{
layout: "vertical",
label: "donate"
}}
createOrder={(data, actions) => {
return actions.order
.create({
purchase_units: [{
amount: {
value: "2",
currency_code: "USD",
},
items: [{
name: "donation-example",
quantity: "1",
unit_amount: {
currency_code: "USD",
value: "2",
},
category: "DONATION",
}],
}],
})
}}
/>;

I had the same problem and I didn't see how exactly the accepted answer here could work with React because of the reference error: PayPal.Donation.Button is undefined, and the code wouldn't compile if I tried using it as a function.
I looked into appending the whole script tag as a string (dangerouslySetInnerHTML) and found this blog post to be a great explanation: Render dangerous content with React. The author makes a pretty cool use of document.Range API in order to solve the problem of executing scripts inside html. He has also published his solution as a small package: dangerously-set-html-content.
I gave it a try in my Gatsby project. First thing I'm adding the Donate SDK script tag on the body in gatsby-srr.js in order to fetch the JapaScript on page load:
exports.onRenderBody = ({setPreBodyComponents}) => {
setPreBodyComponents(
<script key={0} src="https://www.paypalobjects.com/donate/sdk/donate-sdk.js" charSet="UTF-8"/>
)
}
(I need the bizarre-looking key attribute to avoid errors when html.js is compiled. Once it's merged, the attribute is removed)
And then in the component I'm setting the script as a string:
import React from 'react'
import InnerHTML from 'dangerously-set-html-content'
const html = `
<div id="donate-button-container">
<div id="donate-button">
<script>
PayPal.Donation.Button({
...
}}).render('#donate-button')
</script>
</div>
</div>
`
function MyComponent() {
return (
<InnerHTML html={html} />
)
}
You can have a look at the GitHub repo of the package for the explanation of how useEffect and useRef hooks are used, and for some usage examples.

Related

How to create a route link for scrolling to an in-page element?

Let's say I have a page with an element like this:
...
<div id="dummy">
</div>
...
I can create a link that scrolls to #dummy like this http://website.com/page#dummy.
My question is, how could I make a link in the form of http://website.com/page/dummy and behaves exactly the same as the above link (links to page and just scrolls to #dummy)?
Also I want the url of the page to change to http://website.com/page/dummy if the user scrolled to #dummy element.
Is there something readily available in Next.js or React that allows me to do this?
One way of doing this is with redirects. Just create next.config.js and add following configuration
module.exports = {
async redirects() {
return [
{
source: "/page/:slug",
destination: "/page#:slug",
permanent: true,
},
];
},
};

React Calendly Package infinitely loading

I am currently using the react-calendly package and everything is working as expected. I am able to pick a date, etc. and post the event to the calendar. The only issue is below the calendar there are 3 gray dots showing the widget is loading. They never go away, the widget appears to be in a constant state of loading. Is there a reason this is happening? Or maybe could offer a separate solution as opposed to the package I am using. Click https://m44fc.csb.app/ to see what I am talking about. Here is the code pen if you would like to test a solution https://codesandbox.io/s/dreamy-dewdney-m44fc?file=/src/App.js
My code is as follows:
import React, { useState } from 'react'
import { InlineWidget } from 'react-calendly'
function App() {
return (
<>
<InlineWidget
url='https://calendly.com/myURL'
utm={{
utmCampaign: 'Spring Sale 2019',
utmContent: 'Shoe and Shirts',
utmMedium: 'Ad',
utmSource: 'Facebook',
utmTerm: 'Spring'
}}
/>
</>
)
}
export default App
getting the following issue in my chrome dev tools:
Resolve this issue by updating the attributes of the cookie:
Specify SameSite=None and Secure if the cookie is intended to be set in cross-site contexts. Note that only cookies sent over HTTPS may use the Secure attribute.
Specify SameSite=Strict or SameSite=Lax if the cookie should not be set by cross-site requests
Is there something I need to do to resolve these?
Looks like the React component is missing an important style property. We'll look into patching it, but in the meantime the workaround is to pass the following styles yourself when you're using the component:
<InlineWidget
url="https://calendly.com/robert-612/complimentary-consultation"
utm={{
utmCampaign: "Spring Sale 2019",
utmContent: "Shoe and Shirts",
utmMedium: "Ad",
utmSource: "Facebook",
utmTerm: "Spring"
}}
styles={{
minWidth: "320px",
height: '630px',
position: 'relative',
}}
/>
The minWidth and height values are taken from the component's defaults, the position: relative is what was missing. This should ensure that the widget always covers the loading dots, no matter what the page size is.

Golden layout popouts with AngularJS

I am using golden layout in single page application. Golden layout normal 'open in new window' works pretty well (https://jsfiddle.net/shafaq_kazmi/xs5r6mma/6/)
var myLayout = new GoldenLayout({
content: []
}, $('#layoutContainer'));
myLayout.registerComponent('example', function(container, state) {
container.getElement().html('<h2>Hello World</h2>');
});
myLayout.createDragSource($("#button"), {
type: 'component',
componentName: 'example',
componentState: {
text: 'Button'
}
});
myLayout.init();
But when I am trying to integrate it in SPA, when I popout any widget, the whole application gets loaded in popup window instead of the particular widget. Do I need to have some specific configurations to fix this behavior? How can I achieve the actual popout feature.
Any help on this?
Here is PR which fixed the issue with GL when used with SPA. Use latest js from github and try again
https://github.com/deepstreamIO/golden-layout/pull/152

How to make the node modules working in AngularJS project?

I want to use Smart App Banner in my AngularJS project, smart-app-banner uses npm to manage itself.
The guide is very simple and all in one html file. However, in real project, we need to put each file in the right place.
CSS (my project uses scss)
In sample, there is one line in head in html file:
<link rel="stylesheet" href="node_modules/smart-app-banner/smart-app-banner.css" type="text/css" media="screen">
So in my project, I import this css file in app.scss:
#import "node_modules/smart-app-banner/smart-app-banner";
JS (my project uses ECMAScript 6)
In sample, there are two part for JS in body in html file:
First Part:
<script src="node_modules/smart-app-banner/smart-app-banner.js"></script>
So in my project, I import this js file in app.js:
import "smart-app-banner";
Second Part:
<script type="text/javascript">
new SmartBanner({
daysHidden: 15, // days to hide banner after close button is clicked (defaults to 15)
daysReminder: 90, // days to hide banner after "VIEW" button is clicked (defaults to 90)
appStoreLanguage: 'us', // language code for the App Store (defaults to user's browser language)
title: 'MyPage',
author: 'MyCompany LLC',
button: 'VIEW',
store: {
ios: 'On the App Store',
android: 'In Google Play',
windows: 'In Windows store'
},
price: {
ios: 'FREE',
android: 'FREE',
windows: 'FREE'
}
// , force: 'ios' // Uncomment for platform emulation
});
</script>
So in my project, I create a new js file called smart-banner.js in the same directory as app.js file, and put this code in, then import the js file in app.js
import "./smart-banner";
smart-banner.js:
new SmartBanner({
daysHidden: 15, // days to hide banner after close button is clicked (defaults to 15)
daysReminder: 90, // days to hide banner after "VIEW" button is clicked (defaults to 90)
appStoreLanguage: 'us', // language code for the App Store (defaults to user's browser language)
title: 'MyPage',
author: 'MyCompany LLC',
button: 'VIEW',
store: {
ios: 'On the App Store',
android: 'In Google Play',
windows: 'In Windows store'
},
price: {
ios: 'FREE',
android: 'FREE',
windows: 'FREE'
}
// , force: 'ios' // Uncomment for platform emulation
});
But, it's not working. The banner didn't display correctly. Is any step wrong? How to check these process step by step to make sure every file works correctly?
I have found the answer. The problem happened on the import of JS file.
Because it is ECMAScript 6 with babel, in the documentation, it says:
Import an entire module's contents. This inserts myModule into the
current scope, containing all the exported bindings from
"my-module.js".
import * as myModule from "my-module";
Import a single member of a module. This inserts myMember into the
current scope.
import {myMember} from "my-module";
Import an entire module for side effects only, without importing any
bindings.
import "my-module";
So it didn't work if only import the js file. Instead, it need import the class
import SmartBanner from smart-app-banner;
What's more, you cannot put the import in app.js, you need put it in the js file you uses the class.

Custom links to create defect in an app with sdk 2.0

I wrote an application which displays testCases in a treePanel. In that pannel, I have a specific column with icons for actions user can perform on a test case (show details, edit, add a bug...). In the column config, the renderer call this function, where data is an object with a _ref attribut like /testcase/123456/ :
_gridDataFormatTestCaseIcons:function(data)
{
var IconsString = Ext.String.format('<span class="icon-testCase"></span>', Rally.nav.Manager.getDetailUrl(data));
IconsString+= ' ';
IconsString+= '<span class="icon-edit"></span>';
IconsString+= ' ';
IconsString+= '<span class="icon-defect"></span>';
return IconsString ;
}
My first icon opens a new tab with details about the TestCase : OK. My 2nd icon opens a popup where I can edit my TestCase : OK. My third icon opens a popup where I can create a new bug. OK, but... I need to fill all the fileds, included the ones I guess it could be filled automatically.
So my question is about the third icon and the arguments of the function Rally.nav.Manager.create('defect') : the SDK 2.0rc2 docs here says it can take another argument args but don't give any details about it. Can i use it to specify the Owner and Test Case fields for example and how ?
You are correct, the args parameter can be an object that includes default attributes to populate into the Create Dialog. Only a limited subset of fields are accepted however:
Allowed args keys
User Story:
defaultName
rank
iteration
release
parent
dpyOid {dependency}
Defect:
defaultName
defectSuiteOid {Defect Suites}
testCaseResult
testCase
requirement
iteration
Defect Suite:
defaultName
rank
iteration
Portfolio Item:
defaultName
rank
parent
Task:
workProduct
Test Case:
testfolderOid {Test Folder}
artifactOid {Artifact}
It looks like the docs are a bit misleading, i.e., since we're creating a new object, instead of including a ref to an existing defect:
//Launch the create dialog
Rally.nav.Manager.create('/defect/12345');
The docs should read:
//Launch the create dialog
Rally.nav.Manager.create('defect');
Here's a quick example that pops and editor using a Button and sets the Defect Name and Default Iteration. Note that the Rally.nav.Manager functions generally only work when the app is installed and running inside Rally.
<!DOCTYPE html>
<html>
<head>
<title>Create Example</title>
<script type="text/javascript" src="https://rally1.rallydev.com/apps/2.0rc2/sdk.js"></script>
<script type="text/javascript">
Rally.onReady(function() {
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
var myButtonContainer = Ext.create('Ext.Container', {
items: [{
xtype: 'rallybutton',
text: 'Click Here to Create a New Defect',
handler: function() {
//Launch the create dialog
var defectDefaults = {
defaultName: "My Defect",
iteration: "12345678910"
};
Rally.nav.Manager.create('defect', defectDefaults);
}
}],
});
this.add(myButtonContainer);
}
});
Rally.launchApp('CustomApp', {
name: 'Create Example'
});
});
</script>
<style type="text/css">
</style>
</head>
<body></body>
</html>

Resources