diff --git a/web/assets/static/paypal.png b/web/assets/static/paypal.png new file mode 100644 index 00000000..a9a4b321 Binary files /dev/null and b/web/assets/static/paypal.png differ diff --git a/web/assets/static/stripe.png b/web/assets/static/stripe.png new file mode 100644 index 00000000..9cc4bf5e Binary files /dev/null and b/web/assets/static/stripe.png differ diff --git a/web/declrations.d.ts b/web/declrations.d.ts index aa185409..5dc864a6 100644 --- a/web/declrations.d.ts +++ b/web/declrations.d.ts @@ -18,6 +18,7 @@ // declaration.d.ts declare module '*.scss'; +declare module '*.png'; // globals defined by webpack-define-plugin declare const __STRIPE_PUBLIC_KEY__: string; diff --git a/web/src/components/Icons/CreditCard.js b/web/src/components/Icons/CreditCard.tsx similarity index 93% rename from web/src/components/Icons/CreditCard.js rename to web/src/components/Icons/CreditCard.tsx index 3e94e6e7..97cae8d8 100644 --- a/web/src/components/Icons/CreditCard.js +++ b/web/src/components/Icons/CreditCard.tsx @@ -18,7 +18,9 @@ import React from 'react'; -const Icon = ({ fill, width, height, className }) => { +import { IconProps } from './types'; + +const Icon = ({ fill, width, height, className }: IconProps) => { const h = `${height}px`; const w = `${width}px`; diff --git a/web/src/components/Subscription/Checkout/Form.scss b/web/src/components/Subscription/Checkout/Form.scss index 5b30a572..8d6941c9 100644 --- a/web/src/components/Subscription/Checkout/Form.scss +++ b/web/src/components/Subscription/Checkout/Form.scss @@ -51,6 +51,11 @@ font-weight: 600; } +.instruction { + margin-top: rem(8px); + font-weight: 600; +} + .content-wrapper { @include breakpoint(lg) { padding-right: rem(52px); @@ -70,3 +75,50 @@ .label { @include font-size('medium'); } + +.method-wrapper { + margin-top: rem(20px); + display: flex; + + @include breakpoint(lg) { + display: block; + } + + .method { + @include font-size('regular'); + width: 152px; + padding-left: 0; + padding-right: 0; + flex: 1; + font-weight: 600; + color: $gray; + display: inline-flex; + justify-content: center; + align-items: center; + + @include breakpoint(lg) { + width: 200px; + flex: auto; + } + + input { + display: none; + } + } + + label.method-active { + border-color: $first; + } + + .method-text { + margin-left: rem(8px); + } +} + +.stripe-logo { + margin-top: rem(12px); +} + +.paypal-logo { + width: 80px; +} diff --git a/web/src/components/Subscription/Checkout/Form.tsx b/web/src/components/Subscription/Checkout/Form.tsx index c827477a..f2689309 100644 --- a/web/src/components/Subscription/Checkout/Form.tsx +++ b/web/src/components/Subscription/Checkout/Form.tsx @@ -27,6 +27,7 @@ import services from 'web/libs/services'; import { getHomePath } from 'web/libs/paths'; import Sidebar from './Sidebar'; import Flash from '../../Common/Flash'; +import CreditCardIcon from '../../Icons/CreditCard'; import { getCurrentUser } from '../../../store/auth'; import { useDispatch } from '../../../store'; import { setMessage } from '../../../store/ui'; @@ -34,17 +35,21 @@ import NameOnCardInput from '../../Common/PaymentInput/NameOnCard'; import CardInput from '../../Common/PaymentInput/Card'; import Footer from '../Footer'; import CountryInput from '../../Common/PaymentInput/Country'; +import { PaymentMethod } from './helpers'; +import PaymentMethodComponent from './PaymentMethod'; import styles from './Form.scss'; interface Props extends RouteComponentProps { stripe: any; stripeLoadError: string; + paypalLoadError: string; history: History; } const Form: React.FunctionComponent = ({ stripe, stripeLoadError, + paypalLoadError, history }) => { const [nameOnCard, setNameOnCard] = useState(''); @@ -55,6 +60,7 @@ const Form: React.FunctionComponent = ({ const [errMessage, setErrMessage] = useState(''); const dispatch = useDispatch(); const [yearly, setYearly] = useState(false); + const [paymentMethod, setPaymentMethod] = useState(PaymentMethod.CreditCard); async function handleSubmit(e) { e.preventDefault(); @@ -142,36 +148,77 @@ const Form: React.FunctionComponent = ({ kind="danger" wrapperClassName={styles.flash} > - Failed to load stripe. {stripeLoadError} + Failed to load Stripe. {stripeLoadError} + + + Failed to load PayPal. {paypalLoadError}

Welcome to Dnote Pro.

+

+ Please complete payment details. +

-
- +
+ + + Credit card + - - - + + PayPal +
+ + {paymentMethod === PaymentMethod.CreditCard && ( +
+ + + + + + + Stripe +
+ )}
@@ -181,6 +228,7 @@ const Form: React.FunctionComponent = ({ transacting={transacting} yearly={yearly} setYearly={setYearly} + paymentMethod={paymentMethod} />
diff --git a/web/src/components/Subscription/Checkout/PaymentMethod.tsx b/web/src/components/Subscription/Checkout/PaymentMethod.tsx new file mode 100644 index 00000000..244ecc5a --- /dev/null +++ b/web/src/components/Subscription/Checkout/PaymentMethod.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import classnames from 'classnames'; + +import { PaymentMethod } from './helpers'; +import styles from './Form.scss'; + +interface Props { + method: PaymentMethod; + isActive: boolean; + setMethod: (PaymentMethod) => void; +} + +const Component: React.SFC = ({ + children, + method, + isActive, + setMethod +}) => { + return ( + + ); +}; + +export default Component; diff --git a/web/src/components/Subscription/Checkout/Sidebar.scss b/web/src/components/Subscription/Checkout/Sidebar.scss index 0402bf05..f4941ef2 100644 --- a/web/src/components/Subscription/Checkout/Sidebar.scss +++ b/web/src/components/Subscription/Checkout/Sidebar.scss @@ -72,7 +72,6 @@ } .purchase-button { - margin-top: rem(20px); } .assurance { @@ -129,3 +128,7 @@ @include font-size('small'); color: $gray; } + +.paypal-button-container { + margin-top: rem(12px); +} diff --git a/web/src/components/Subscription/Checkout/Sidebar.tsx b/web/src/components/Subscription/Checkout/Sidebar.tsx index 59d19674..0b9574f0 100644 --- a/web/src/components/Subscription/Checkout/Sidebar.tsx +++ b/web/src/components/Subscription/Checkout/Sidebar.tsx @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -import React from 'react'; +import React, { useEffect } from 'react'; import classnames from 'classnames'; import Button from '../../Common/Button'; @@ -24,6 +24,7 @@ import Toggle, { ToggleKind } from '../../Common/Toggle'; import PaymentSummary from './PaymentSummary'; import Price from './Price'; import ScheduleSummary from './ScheduleSummary'; +import { PaymentMethod } from './helpers'; import styles from './Sidebar.scss'; interface Props { @@ -31,9 +32,31 @@ interface Props { transacting: boolean; yearly: boolean; setYearly: (boolean) => null; + paymentMethod: PaymentMethod; } -function Sidebar({ isReady, transacting, yearly, setYearly }) { +const paypalButtonNodeID = 'paypal-button-container'; + +function Sidebar({ isReady, transacting, yearly, setYearly, paymentMethod }) { + useEffect(() => { + if (paymentMethod === PaymentMethod.PayPal) { + (window as any).paypal + .Buttons({ + style: { + layout: 'vertical', + color: 'blue', + shape: 'rect', + label: 'paypal' + } + }) + .render(`#${paypalButtonNodeID}`); + } else { + const node = document.getElementById(paypalButtonNodeID); + + node.innerHTML = ''; + } + }, [paymentMethod]); + return (
@@ -87,20 +110,27 @@ function Sidebar({ isReady, transacting, yearly, setYearly }) { - +
+ + {paymentMethod === PaymentMethod.CreditCard && ( + + )}

You can cancel auto-renewal any time.

diff --git a/web/src/components/Subscription/Checkout/helpers.ts b/web/src/components/Subscription/Checkout/helpers.ts new file mode 100644 index 00000000..02631b47 --- /dev/null +++ b/web/src/components/Subscription/Checkout/helpers.ts @@ -0,0 +1,5 @@ +// PaymentMethod represents the possible payment methods +export enum PaymentMethod { + CreditCard = 'creditCard', + PayPal = 'paypal' +} diff --git a/web/src/components/Subscription/Checkout/index.tsx b/web/src/components/Subscription/Checkout/index.tsx index ade77300..df46bde1 100644 --- a/web/src/components/Subscription/Checkout/index.tsx +++ b/web/src/components/Subscription/Checkout/index.tsx @@ -28,9 +28,11 @@ import CheckoutForm from './Form'; const Checkout: React.FunctionComponent = () => { const [stripeLoaded, stripeLoadError] = useScript('https://js.stripe.com/v3'); const [paypalLoaded, paypalLoadError] = useScript( - `https://www.paypal.com/sdk/js?client-id=${__PAYPAL_CLIENT_ID__}` + `https://www.paypal.com/sdk/js?client-id=${__PAYPAL_CLIENT_ID__}&vault=true` ); + console.log(paypalLoaded); + const key = `${__STRIPE_PUBLIC_KEY__}`; let stripe = null; @@ -52,7 +54,10 @@ const Checkout: React.FunctionComponent = () => { return ( - + ); diff --git a/web/webpack/rules/image.js b/web/webpack/rules/image.js index 116b3e1b..cfbf8831 100644 --- a/web/webpack/rules/image.js +++ b/web/webpack/rules/image.js @@ -18,12 +18,15 @@ const PATHS = require('../paths'); -module.exports = ({ limit = 10000 } = {}) => ({ +module.exports = () => ({ test: /\.(bmp|gif|jpg|jpeg|png|svg)$/, use: [ { - loader: 'url-loader', - options: { name: '[hash].[ext]', limit } + loader: 'file-loader', + options: { + publicPath: PATHS.public, + outputPath: PATHS.public + } } ] });