Skip to content
This repository was archived by the owner on Feb 3, 2022. It is now read-only.

Feat/add shopping cart example #3

Merged
merged 3 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Refactor and add getStripe singleton util.
  • Loading branch information
thorsten-stripe committed Jun 16, 2020
commit fde931ffa6de788ae832bcb4f58988a162c3d70a
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Read more about testing on Stripe at https://stripe.com/docs/testing.
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

```bash
npm init next-app --example with-stripe-typescript with-stripe-typescript-app
npx create-next-app --example with-stripe-typescript with-stripe-typescript-app
# or
yarn create next-app --example with-stripe-typescript with-stripe-typescript-app
```
Expand Down
16 changes: 16 additions & 0 deletions components/Cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, { ReactNode } from 'react';
import { CartProvider } from 'use-shopping-cart';
import getStripe from '../utils/get-stripejs';
import * as config from '../config';

const Cart = ({ children }: { children: ReactNode }) => (
<CartProvider
mode="checkout-session"
stripe={getStripe()}
currency={config.CURRENCY}
>
<>{children}</>
</CartProvider>
);

export default Cart;
9 changes: 4 additions & 5 deletions components/CheckoutForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ import React, { useState } from 'react';

import CustomDonationInput from '../components/CustomDonationInput';

import getStripe from '../utils/get-stripejs';
import { fetchPostJSON } from '../utils/api-helpers';
import { formatAmountForDisplay } from '../utils/stripe-helpers';
import * as config from '../config';

import { useStripe } from '@stripe/react-stripe-js';

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

const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) =>
setInput({
Expand All @@ -35,6 +33,7 @@ const CheckoutForm: React.FunctionComponent = () => {
}

// Redirect to Checkout.
const stripe = await getStripe();
const { error } = await stripe!.redirectToCheckout({
// Make the id field from the Checkout Session creation API response
// available to this file, so you can provide it as parameter here
Expand Down Expand Up @@ -63,7 +62,7 @@ const CheckoutForm: React.FunctionComponent = () => {
<button
className="checkout-style-background"
type="submit"
disabled={!stripe || loading}
disabled={loading}
>
Donate {formatAmountForDisplay(input.customDonation, config.CURRENCY)}
</button>
Expand Down
30 changes: 15 additions & 15 deletions components/CustomDonationInput.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React from 'react'
import { formatAmountForDisplay } from '../utils/stripe-helpers'
import React from 'react';
import { formatAmountForDisplay } from '../utils/stripe-helpers';

type Props = {
name: string
value: number
min: number
max: number
currency: string
step: number
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
className?: string
}
name: string;
value: number;
min: number;
max: number;
currency: string;
step: number;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
className?: string;
};

const CustomDonationInput: React.FunctionComponent<Props> = ({
const CustomDonationInput = ({
name,
value,
min,
Expand All @@ -21,7 +21,7 @@ const CustomDonationInput: React.FunctionComponent<Props> = ({
step,
onChange,
className,
}) => (
}: Props) => (
<label>
Custom donation amount ({formatAmountForDisplay(min, currency)}-
{formatAmountForDisplay(max, currency)}):
Expand All @@ -45,6 +45,6 @@ const CustomDonationInput: React.FunctionComponent<Props> = ({
onChange={onChange}
></input>
</label>
)
);

export default CustomDonationInput
export default CustomDonationInput;
2 changes: 1 addition & 1 deletion components/ElementsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const CARD_OPTIONS = {
},
};

const ElementsForm: React.FunctionComponent = () => {
const ElementsForm = () => {
const [input, setInput] = useState({
customDonation: Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP),
cardholderName: '',
Expand Down
129 changes: 56 additions & 73 deletions components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,89 +1,72 @@
import React, { ReactNode } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import { CartProvider } from 'use-shopping-cart';
import * as config from '../config';

type Props = {
children: ReactNode;
title?: string;
};

const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);

const Layout = ({
children,
title = 'TypeScript Next.js Stripe Example',
}: Props) => (
<CartProvider
mode="checkout-session"
stripe={stripePromise}
currency={config.CURRENCY}
>
<Elements stripe={stripePromise}>
<Head>
<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>
<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
href="https://github.com/stripe-samples"
target="_blank"
rel="noopener noreferrer"
>
Stripe Sample
</a>
.{' View code on '}
<a
href="https://github.com/vercel/next.js/tree/canary/examples/with-stripe-typescript"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
.
</span>
</div>
</Elements>
</CartProvider>
<>
<Head>
<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>
<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
href="https://github.com/stripe-samples"
target="_blank"
rel="noopener noreferrer"
>
Stripe Sample
</a>
.{' View code on '}
<a
href="https://github.com/vercel/next.js/tree/canary/examples/with-stripe-typescript"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
.
</span>
</div>
</>
);

export default Layout;
16 changes: 8 additions & 8 deletions components/PrintObject.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react'
import React from 'react';

type Props = {
content: object
}
content: object;
};

const PrintObject: React.FunctionComponent<Props> = ({ content }) => {
const formattedContent: string = JSON.stringify(content, null, 2)
return <pre>{formattedContent}</pre>
}
const PrintObject = ({ content }: Props) => {
const formattedContent: string = JSON.stringify(content, null, 2);
return <pre>{formattedContent}</pre>;
};

export default PrintObject
export default PrintObject;
19 changes: 12 additions & 7 deletions pages/donate-with-elements.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { NextPage } from 'next'
import Layout from '../components/Layout'
import { NextPage } from 'next';

import ElementsForm from '../components/ElementsForm'
import { Elements } from '@stripe/react-stripe-js';
import getStripe from '../utils/get-stripejs';

import Layout from '../components/Layout';
import ElementsForm from '../components/ElementsForm';

const DonatePage: NextPage = () => {
return (
<Layout title="Donate with Elements | Next.js + TypeScript Example">
<div className="page-container">
<h1>Donate with Elements</h1>
<p>Donate to our project 💖</p>
<ElementsForm />
<Elements stripe={getStripe()}>
<ElementsForm />
</Elements>
</div>
</Layout>
)
}
);
};

export default DonatePage
export default DonatePage;
2 changes: 1 addition & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const IndexPage: NextPage = () => {
<Link href="/use-shopping-cart">
<a className="card cart-style-background">
<h2 className="bottom">Use Shopping Cart</h2>
<img src="https://use-shopping-cart.netlify.app/banner.png" />
<img src="/use-shopping-cart.png" />
</a>
</Link>
</li>
Expand Down
6 changes: 5 additions & 1 deletion pages/result.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { NextPage } from 'next';
import { useRouter } from 'next/router';

import Layout from '../components/Layout';
import PrintObject from '../components/PrintObject';
import Cart from '../components/Cart';
import ClearCart from '../components/ClearCart';

import { fetchGetJSON } from '../utils/api-helpers';
Expand All @@ -28,7 +30,9 @@ const ResultPage: NextPage = () => {
<h2>Status: {data?.payment_intent?.status ?? 'loading...'}</h2>
<h3>CheckoutSession response:</h3>
<PrintObject content={data ?? 'loading...'} />
<ClearCart />
<Cart>
<ClearCart />
</Cart>
</div>
</Layout>
);
Expand Down
12 changes: 10 additions & 2 deletions pages/use-shopping-cart.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextPage } from 'next';
import Layout from '../components/Layout';

import Cart from '../components/Cart';
import CartSummary from '../components/CartSummary';
import Products from '../components/Products';

Expand All @@ -9,8 +10,15 @@ const DonatePage: NextPage = () => {
<Layout title="Shopping Cart | Next.js + TypeScript Example">
<div className="page-container">
<h1>Shopping Cart</h1>
<CartSummary />
<Products />
<p>
Powered by the{' '}
<a href="https://use-shopping-cart.netlify.app/">use-shopping-cart</a>{' '}
React hooks library.
</p>
<Cart>
<CartSummary />
<Products />
</Cart>
</div>
</Layout>
);
Expand Down
Binary file added public/use-shopping-cart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions utils/get-stripejs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* This is a singleton to ensure we only instantiate Stripe once.
*/
import { loadStripe } from '@stripe/stripe-js';

let stripePromise: Promise<import('@stripe/stripe-js').Stripe | null>;
const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
}
return stripePromise;
};

export default getStripe;