From 1d4cb2b7e6fcd6bd495bb2c0d454cc8d5735d524 Mon Sep 17 00:00:00 2001 From: Sung Won Cho Date: Fri, 7 Feb 2020 16:49:06 +1100 Subject: [PATCH] wip --- web/assets/static/paypal.png | Bin 0 -> 4647 bytes web/assets/static/stripe.png | Bin 0 -> 1477 bytes web/declrations.d.ts | 1 + .../Icons/{CreditCard.js => CreditCard.tsx} | 4 +- .../Subscription/Checkout/Form.scss | 52 ++++++++++ .../components/Subscription/Checkout/Form.tsx | 90 ++++++++++++++---- .../Subscription/Checkout/PaymentMethod.tsx | 43 +++++++++ .../Subscription/Checkout/Sidebar.scss | 5 +- .../Subscription/Checkout/Sidebar.tsx | 62 ++++++++---- .../Subscription/Checkout/helpers.ts | 5 + .../Subscription/Checkout/index.tsx | 9 +- web/webpack/rules/image.js | 9 +- 12 files changed, 236 insertions(+), 44 deletions(-) create mode 100644 web/assets/static/paypal.png create mode 100644 web/assets/static/stripe.png rename web/src/components/Icons/{CreditCard.js => CreditCard.tsx} (93%) create mode 100644 web/src/components/Subscription/Checkout/PaymentMethod.tsx create mode 100644 web/src/components/Subscription/Checkout/helpers.ts diff --git a/web/assets/static/paypal.png b/web/assets/static/paypal.png new file mode 100644 index 0000000000000000000000000000000000000000..a9a4b32131d76b57d96abecf958f78d8faf6524e GIT binary patch literal 4647 zcmb7I2|SeB8=f{zLP>=d#wcxOH3nnc?$|9+pQEJjk@`=1}?`_6ga=e*}V@AJIpXC>EvilP1peH;#F z==8KPgcqx{u~%1 z@L(Y#bs@euT}&h(0vBS4y&u(2<_3!puXs5e81Em%kDtSL5)dcc0DuI>Ac9gN zmMU0E7or-M1)f#I6e2-wg3WOua#RZvLjAY|H&hN2?8){dK9xo%(8x4~6O&4}CD6eu zg+>KGB$^|OMrF|%1nr0Ti#~xZ7euiF+xfj1YT2*YG73MDo+mKHr!3v!oil8_dObLJ$XtbOU19R)IBjiGHTuAIfRBc2C z#mOMCdM`Xk0aIdVUt5CUejb|ISiomRp>hcXa*RkIAxx1;g`E=;RDeVwFeo%|`V11) zkwm8lfupA}SauF%CkB&5bzo672?WVxDUf&=*fOb14FVYAisBRq9}>qR0!)NR5h-5; zD_n@A&b+JKU?nDp_}KT?VUitb-!3D9XfDJkI|hSqM{}avM?pM@PIHK2@~AM+(TTxu za&Y3=J24z#I%FTE3&lTL!h_{#6b9CdhQ#1d)#U;L)|q%BVz@IKfU~-Bt{fH0VTA&T z0sE7pUE|u_iC{!1!VsxIhN$+Yn|IT#5eU|eh2$`cprM+#D%SxpB#5)IR&tzs5T8Y` z5o6BZ5Z`8QG>SO`_G=zg5}V$WpJ9XcgKgQ1I~r&wqV{tw0Y4&wKvLu;r`U8fQPW^`Nd zyUf*Cbh!Yg|GN{QdpAD~-~ZjOC+T%CGM1YhhA>nfgrZ_?2%6L2ickzyh)@~9Es#kV z9|8&xja5wO^7Qj#c}o=-B;~{2?pjwM3uFWlu$(wRjX4f98lA(S(L5Ngo{sLG_8uM_ zH#ZMEdj_)`iaW}WREO6i7lq=luO{FG~wsV1VD?>dF>^Y-(=fZq2| zU!aSff~D4FAevpe;)-Aj87!9}N*J^PyDkCil-o{0`mWw~8fi88Ytd45IhI3$y)t;cd8;SXI3pdYB4@EJv3xhPVniQKEn)`?Ynk-9(!T?+gmZ<)A3w-`HG;p<;07k zxc0`a|17v;e|dI6-jOte(U)BFR*n9>>g-|Ory^`Eqc=Ij6qvW(w+?u_Vy0IBc;noM z529Py5u|uGg?t(%X}J z(c#&(`?qAaL*t`gr*dutj|d+*VN>{hxWur;U`wCO`WcCn7FxZToYD~-V7dF{;1?~j zBOM-p=6UV*<-HGhR5m2*w55r`fXR5Di6t+_)_uAyde|6}eX#MjU3d%n2gS~de~qqr z*>7KZSVmKU{`1?VM;hwTA$dFa${xka9pl_$LD@~iR+9GyViQu^ zwoJc$^4ap8*W2fQ-ad3~(uTa@ct~*I^vkMH=?m8n#z~_#j-IpQ{;TWNggq@W#NWo; zn;L0|UBVM{n?8s z)OW!20Uav_Ce9Do^zOon%YUT&D!RG)j%k`j^I?bev2RRx3enJdkEss@AI;0JFX%tx z*Oj*_BNvyt1#af(ZPb4{&6BgEdgJB$ZM(Be!*+gbco*3Grz!h9F~9U?^tsi?FrWtPEiBBH(tEeYpRhQelAKW#CqDceP6YSN!6a?8=G4{nVnnYh_^NH z{wrm^-cq(%&KJ?+_%!#IR3!NCsiI8TC%u=l3fCjE!jSpK6N*QdH(c|W@Ozxn_{%az zz@<4krEV>y=kWZ;KpM8p9+bQ-BlkUf+A{P~M73qf0)yiU+>A5*`LAP_mr1r}r}~8N zJ5)5J;4@YGQ?vMKC2pI>CYVeLIb~A2{oI<}w(Mu0 zd>?KLvOi=NQRiDZ-%w_BwWHzJh<=~-q-n3VY~mkAC=p?<_6&ywFQ;R&+7t=#|8j zS36lVlNYD>mbK{+d1J&04wJ)Xyf7xb*|BQj5kfT&nv2j<<)u Kd!B3L;{O5vK<)zo literal 0 HcmV?d00001 diff --git a/web/assets/static/stripe.png b/web/assets/static/stripe.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc4bf5e814918873835fd28fdadab10c170ed91 GIT binary patch literal 1477 zcmeAS@N?(olHy`uVBq!ia0vp^bY zMdid7sN~j1bJ$eqYADo7mpMuC%QB1F>hp5(I64@nRr)8?RE7CeD>@fum6)68Sq6y& zxX6`j7Q4lYS@>xMsw&xg=gWtI_r^^7{Kc%F>-d~_Ulti0h_dcu-LNTP)7^>r z&zXx)8_wq375lb@^WCghjyajnUa@&wZ?wH0A(x+@?d-jKhuuPdmJio=D18Yx@m!(7 zc<_3Q_Nh%j#Q(9gef^gBA?*7FgQN_X=SsKS3~k!h>^om4Bk#&%eKJX@$>N+@QMAcxl2QNlLhMqS}iE6xm zHyLqfF86U;`DD()x#fBeY77(3PE(&PIDhB--=%&!YA?M@ZfM=P=NeRTB5}^#6OU7j z^(v+vNew;islE5Zq6b=SORoO8<($xU> zD7;j@KywynMf2|ic)_aD(e#TbU z)mD5}*;CQ5eEIU?;3uc$)!*`TfBms5@S(e1c>k$0c_JGXwm%Z;bW`vn@_y`m-1K4`_Xi6s-D+G(XRDjz3Z0+dHq%vHILVE*15P(=yyuMu7h`F z&L~o__6p@AK<7D(^c?igS1KJIu6P{X<#XrF+DYTO4H2tH*eqB89y`aZ`tR& zdw=ixvQ^{Gi@W>RTeTP7@sBN+FJ0rjU#elJ&HIeIb## L`njxgN@xNAbE8wU literal 0 HcmV?d00001 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 + } } ] });