Skip to content
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

Add global CSS styles to example/with-stripe-typescript #10520

Merged
merged 9 commits into from Feb 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 26 additions & 2 deletions examples/with-stripe-typescript/README.md
@@ -1,7 +1,7 @@
# Example using Stripe with TypeScript and react-stripe-js 🔒💸
# Example using Stripe with TypeScript and react-stripe-js

- Demo: https://nextjs-typescript-react-stripe-js.now.sh/
- CodeSandbox: https://codesandbox.io/s/nextjs-typescript-react-stripe-js-rqrss
- CodeSandbox: https://codesandbox.io/s/github/stripe-samples/nextjs-typescript-react-stripe-js
- Tutorial: https://dev.to/thorwebdev/type-safe-payments-with-next-js-typescript-and-stripe-4jo7

This is a full-stack TypeScript example using:
Expand All @@ -13,8 +13,27 @@ This is a full-stack TypeScript example using:
- Next.js [API routes](https://nextjs.org/docs/api-routes/introduction)
- [stripe-node with TypeScript](https://github.com/stripe/stripe-node#usage-with-typescript)

**Demo**

See the sample [live](https://nextjs-typescript-react-stripe-js.now.sh/) or [fork](https://codesandbox.io/s/github/stripe-samples/nextjs-typescript-react-stripe-js) on CodeSandbox.

The demo is running in test mode -- use `4242424242424242` as a test card number with any CVC + future expiration date.

Use the `4000000000003220` test card number to trigger a 3D Secure challenge flow.

Read more about testing on Stripe at https://stripe.com/docs/testing.

<details open><summary>Checkout Demo</summary>
<img src="./public/checkout_demo.gif" alt="A gif of the Checkout payment page." align="center">
</details>

<details><summary>Elements Demo</summary>
<img src="./public/elements_demo.gif" alt="A gif of the custom Elements checkout page." align="center">
</details>

### Included functionality

- [Global CSS styles](https://nextjs.org/blog/next-9-2#built-in-css-support-for-global-stylesheets)
- Making `.env` variables available to next: [next.config.js](next.config.js)
- **Note**: When deploying with Now you need to [add your secrets](https://zeit.co/docs/v2/serverless-functions/env-and-secrets) and specify a [now.json](/now.json) file.
- Implementation of a Layout component that loads and sets up Stripe.js and Elements for usage with SSR via `loadStripe` helper: [components/Layout.tsx](components/Layout.tsx).
Expand Down Expand Up @@ -116,3 +135,8 @@ now secrets add stripe_webhook_secret whsec_***
```

As the secrets are set as env vars in the project at deploy time, we will need to redeploy our app after we made changes to the secrets.

### Authors

- [@thorsten-stripe](https://twitter.com/thorwebdev)
- [@lfades](https://twitter.com/luis_fades)
11 changes: 9 additions & 2 deletions examples/with-stripe-typescript/components/CheckoutForm.tsx
Expand Up @@ -9,7 +9,9 @@ import * as config from '../config'
import { useStripe } from '@stripe/react-stripe-js'

const CheckoutForm: React.FunctionComponent = () => {
const [input, setInput] = useState({ customDonation: config.MIN_AMOUNT })
const [input, setInput] = useState({
customDonation: Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP),
})
const stripe = useStripe()

const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = e =>
Expand Down Expand Up @@ -46,6 +48,7 @@ const CheckoutForm: React.FunctionComponent = () => {
return (
<form onSubmit={handleSubmit}>
<CustomDonationInput
className="checkout-style"
name={'customDonation'}
value={input.customDonation}
min={config.MIN_AMOUNT}
Expand All @@ -54,7 +57,11 @@ const CheckoutForm: React.FunctionComponent = () => {
currency={config.CURRENCY}
onChange={handleInputChange}
/>
<button type="submit" disabled={!stripe}>
<button
className="checkout-style-background"
type="submit"
disabled={!stripe}
>
Donate {formatAmountForDisplay(input.customDonation, config.CURRENCY)}
</button>
</form>
Expand Down
Expand Up @@ -9,6 +9,7 @@ type Props = {
currency: string
step: number
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
className?: string
}

const CustomDonationInput: React.FunctionComponent<Props> = ({
Expand All @@ -19,11 +20,13 @@ const CustomDonationInput: React.FunctionComponent<Props> = ({
currency,
step,
onChange,
className,
}) => (
<label>
Custom donation amount ({formatAmountForDisplay(min, currency)}-
{formatAmountForDisplay(max, currency)}):
<input
className={className}
type="number"
name={name}
value={value}
Expand All @@ -32,6 +35,15 @@ const CustomDonationInput: React.FunctionComponent<Props> = ({
step={step}
onChange={onChange}
></input>
<input
type="range"
name={name}
value={value}
min={min}
max={max}
step={step}
onChange={onChange}
></input>
</label>
)

Expand Down
76 changes: 49 additions & 27 deletions examples/with-stripe-typescript/components/ElementsForm.tsx
Expand Up @@ -9,9 +9,33 @@ import * as config from '../config'

import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'

const CARD_OPTIONS = {
iconStyle: 'solid' as const,
style: {
base: {
iconColor: '#6772e5',
color: '#6772e5',
fontWeight: '500',
fontFamily: 'Roboto, Open Sans, Segoe UI, sans-serif',
fontSize: '16px',
fontSmoothing: 'antialiased',
':-webkit-autofill': {
color: '#fce883',
},
'::placeholder': {
color: '#6772e5',
},
},
invalid: {
iconColor: '#ef2961',
color: '#ef2961',
},
},
}

const ElementsForm: React.FunctionComponent = () => {
const [input, setInput] = useState({
customDonation: config.MIN_AMOUNT,
customDonation: Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP),
cardholderName: '',
})
const [payment, setPayment] = useState({ status: 'initial' })
Expand All @@ -36,7 +60,7 @@ const ElementsForm: React.FunctionComponent = () => {
return (
<>
<h2>Error 😭</h2>
<p>{errorMessage}</p>
<p className="error-message">{errorMessage}</p>
</>
)

Expand All @@ -53,6 +77,8 @@ const ElementsForm: React.FunctionComponent = () => {

const handleSubmit: React.FormEventHandler<HTMLFormElement> = async e => {
e.preventDefault()
// Abort if form isn't valid
if (!e.currentTarget.reportValidity()) return
setPayment({ status: 'processing' })

// Create a PaymentIntent with the specified amount.
Expand Down Expand Up @@ -95,6 +121,7 @@ const ElementsForm: React.FunctionComponent = () => {
<>
<form onSubmit={handleSubmit}>
<CustomDonationInput
className="elements-style"
name="customDonation"
value={input.customDonation}
min={config.MIN_AMOUNT}
Expand All @@ -103,35 +130,30 @@ const ElementsForm: React.FunctionComponent = () => {
currency={config.CURRENCY}
onChange={handleInputChange}
/>
<fieldset>
<fieldset className="elements-style">
<legend>Your payment details:</legend>
<label>
Cardholder name:
<input
type="Text"
name="cardholderName"
onChange={handleInputChange}
required={true}
/>
</label>
<CardElement
options={{
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': {
color: '#aab7c4',
},
},
invalid: {
color: '#9e2146',
},
},
}}
<input
placeholder="Cardholder name"
className="elements-style"
type="Text"
name="cardholderName"
onChange={handleInputChange}
required
/>
<div className="FormRow elements-style">
<CardElement
options={CARD_OPTIONS}
onChange={e => {
if (e.error) {
setPayment({ status: 'error' })
setErrorMessage(e.error.message ?? 'An unknown error occured')
}
}}
/>
</div>
</fieldset>
<button
className="elements-style-background"
type="submit"
disabled={
!['initial', 'succeeded', 'error'].includes(payment.status) ||
Expand Down
55 changes: 33 additions & 22 deletions examples/with-stripe-typescript/components/Layout.tsx
@@ -1,6 +1,6 @@
import React from 'react'
import Link from 'next/link'
import Head from 'next/head'
import Link from 'next/link'
import { Elements } from '@stripe/react-stripe-js'
import { loadStripe } from '@stripe/stripe-js'

Expand All @@ -19,25 +19,36 @@ const Layout: React.FunctionComponent<Props> = ({
<title>{title}</title>
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@thorwebdev" />
<meta name="twitter:title" content="TypeScript Next.js Stripe Example" />
<meta
name="twitter:description"
content="Full-stack TypeScript example using Next.js, react-stripe-js, and stripe-node."
/>
<meta
name="twitter:image"
content="https://nextjs-typescript-react-stripe-js.now.sh/social_card.png"
/>
</Head>
<header>
<nav>
<Link href="/">
<a>Home</a>
</Link>{' '}
|{' '}
<Link href="/donate-with-checkout">
<a>Donate with Checkout</a>
</Link>{' '}
|{' '}
<Link href="/donate-with-elements">
<a>Donate with Elements</a>
</Link>
</nav>
</header>
{children}
<footer>
<hr />
<div className="container">
<header>
<div className="header-content">
<Link href="/">
<a className="logo">
<img src="/logo.png" />
</a>
</Link>
<h1>
<span className="light">Stripe Sample</span>
<br />
Next.js, TypeScript, and Stripe 🔒💸
</h1>
</div>
</header>
{children}
</div>
<div className="banner">
<span>
This is a{' '}
<a
Expand All @@ -47,17 +58,17 @@ const Layout: React.FunctionComponent<Props> = ({
>
Stripe Sample
</a>
.{' '}
.{' View code on '}
<a
href="https://github.com/zeit/next.js/tree/canary/examples/with-stripe-typescript"
target="_blank"
rel="noopener noreferrer"
>
View code on GitHub
GitHub
</a>
.
</span>
</footer>
</div>
</Elements>
)

Expand Down
2 changes: 1 addition & 1 deletion examples/with-stripe-typescript/package.json
Expand Up @@ -17,7 +17,7 @@
"next": "latest",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"stripe": "^8.8.1",
"stripe": "^8.15.0",
"swr": "^0.1.16"
},
"devDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions examples/with-stripe-typescript/pages/_app.tsx
@@ -0,0 +1,9 @@
import { AppProps } from 'next/app'

import '../styles.css'

function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}

export default MyApp
@@ -1,12 +1,9 @@
import { NextApiRequest, NextApiResponse } from 'next'

import Stripe from 'stripe'
const stripeSecretKey: string = process.env.STRIPE_SECRET_KEY!
const stripe = new Stripe(stripeSecretKey, {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: '2019-12-03',
typescript: true,
telemetry: true,
})

export default async (req: NextApiRequest, res: NextApiResponse) => {
Expand Down
Expand Up @@ -4,12 +4,9 @@ import { CURRENCY, MIN_AMOUNT, MAX_AMOUNT } from '../../../config'
import { formatAmountForStripe } from '../../../utils/stripe-helpers'

import Stripe from 'stripe'
const stripeSecretKey: string = process.env.STRIPE_SECRET_KEY!
const stripe = new Stripe(stripeSecretKey, {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: '2019-12-03',
typescript: true,
telemetry: true,
})

export default async (req: NextApiRequest, res: NextApiResponse) => {
Expand Down
Expand Up @@ -4,12 +4,9 @@ import { CURRENCY, MIN_AMOUNT, MAX_AMOUNT } from '../../../config'
import { formatAmountForStripe } from '../../../utils/stripe-helpers'

import Stripe from 'stripe'
const stripeSecretKey: string = process.env.STRIPE_SECRET_KEY!
const stripe = new Stripe(stripeSecretKey, {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: '2019-12-03',
typescript: true,
telemetry: true,
})

export default async (req: NextApiRequest, res: NextApiResponse) => {
Expand Down