Skip to content

Commit 550ac21

Browse files
Merge pull request #4 from danielballoch/add-stripe
Add local postgres db
2 parents bcd5959 + 5af55b3 commit 550ac21

22 files changed

+1157
-1743
lines changed

components/CardSectionStyles.css

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Shows how you can use CSS to style your Element's container.
3+
* These classes are added to your Stripe Element by default.
4+
* You can override these classNames by using the options passed
5+
* to the CardElement component.
6+
* https://stripe.com/docs/js/elements_object/create_element?type=card#elements_create-options-classes
7+
*/
8+
9+
.StripeElement {
10+
height: 40px;
11+
padding: 10px 12px;
12+
width: 100%;
13+
color: #32325d;
14+
background-color: white;
15+
border: 1px solid transparent;
16+
border-radius: 4px;
17+
18+
box-shadow: 0 1px 3px 0 #e6ebf1;
19+
-webkit-transition: box-shadow 150ms ease;
20+
transition: box-shadow 150ms ease;
21+
}
22+
23+
.StripeElement--focus {
24+
box-shadow: 0 1px 3px 0 #cfd7df;
25+
}
26+
27+
.StripeElement--invalid {
28+
border-color: #fa755a;
29+
}
30+
31+
.StripeElement--webkit-autofill {
32+
background-color: #fefde5 !important;
33+
}

components/CardSelection.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Use the CSS tab above to style your Element's container.
3+
*/
4+
import React from 'react';
5+
import {CardElement} from '@stripe/react-stripe-js';
6+
// import './CardSectionStyles.css'
7+
8+
const CARD_ELEMENT_OPTIONS = {
9+
style: {
10+
base: {
11+
color: "#32325d",
12+
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
13+
fontSmoothing: "antialiased",
14+
fontSize: "16px",
15+
"::placeholder": {
16+
color: "#aab7c4",
17+
},
18+
},
19+
invalid: {
20+
color: "#fa755a",
21+
iconColor: "#fa755a",
22+
},
23+
},
24+
};
25+
26+
function CardSection() {
27+
return (
28+
<div>
29+
<label>
30+
Card details
31+
<CardElement options={CARD_ELEMENT_OPTIONS} />
32+
</label>
33+
<style jsx global>{`
34+
.StripeElement {
35+
height: 40px;
36+
padding: 10px 12px;
37+
width: 100%;
38+
color: #32325d;
39+
background-color: white;
40+
border: 1px solid transparent;
41+
border-radius: 4px;
42+
43+
box-shadow: 0 1px 3px 0 #e6ebf1;
44+
-webkit-transition: box-shadow 150ms ease;
45+
transition: box-shadow 150ms ease;
46+
}
47+
48+
.StripeElement--focus {
49+
box-shadow: 0 1px 3px 0 #cfd7df;
50+
}
51+
52+
.StripeElement--invalid {
53+
border-color: #fa755a;
54+
}
55+
56+
.StripeElement--webkit-autofill {
57+
background-color: #fefde5 !important;
58+
}
59+
`}</style>
60+
</div>
61+
);
62+
};
63+
64+
export default CardSection;

components/CheckoutForm.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.sr-combo-inputs {
2+
margin: 20px 0;
3+
}
4+
5+
.sr-input {
6+
font-size: 16px;
7+
}
8+
9+
.sr-card-element {
10+
padding-top: 12px;
11+
}
12+
13+
.btn {
14+
font-size: 16px;
15+
}

components/CheckoutForm.js

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import React, { useEffect, useState } from "react";
2+
import {useStripe, useElements, CardElement} from '@stripe/react-stripe-js';
3+
import getUserLocale from 'get-user-locale';
4+
// import "./CheckoutForm.css";
5+
import CardSection from './CardSelection';
6+
7+
import api from "../lib/stripe-api";
8+
9+
10+
export default function CheckoutForm() {
11+
12+
const [amount, setAmount] = useState(0);
13+
const [currency, setCurrency] = useState("");
14+
const [clientSecret, setClientSecret] = useState(null);
15+
const [error, setError] = useState(null);
16+
const [metadata, setMetadata] = useState(null);
17+
const [succeeded, setSucceeded] = useState(false);
18+
const [processing, setProcessing] = useState(false);
19+
const stripe = useStripe();
20+
const elements = useElements();
21+
22+
const userLocale = getUserLocale();
23+
24+
useEffect(() => {
25+
// Step 1: Fetch product details such as amount and currency from
26+
// API to make sure it can't be tampered with in the client.
27+
api.getProductDetails().then(productDetails => {
28+
setAmount(productDetails.amount / 100);
29+
setCurrency(productDetails.currency);
30+
});
31+
32+
// Step 2: Create PaymentIntent over Stripe API
33+
api
34+
.createPaymentIntent({
35+
payment_method_types: ["card"]
36+
})
37+
.then(clientSecret => {
38+
setClientSecret(clientSecret);
39+
})
40+
.catch(err => {
41+
setError(err.message);
42+
});
43+
}, []);
44+
45+
46+
const handleSubmit = async ev => {
47+
// We don't want to let default form submission happen here,
48+
// which would refresh the page.
49+
ev.preventDefault();
50+
setProcessing(true);
51+
52+
// Step 3: Use clientSecret from PaymentIntent and the CardElement
53+
// to confirm payment with stripe.confirmCardPayment()
54+
const payload = await stripe.confirmCardPayment(clientSecret, {
55+
payment_method: {
56+
card: elements.getElement(CardElement),
57+
billing_details: {
58+
name: ev.target.name.value
59+
}
60+
}
61+
});
62+
63+
if (payload.error) {
64+
setError(`Payment failed: ${payload.error.message}`);
65+
setProcessing(false);
66+
console.log("[error]", payload.error);
67+
} else {
68+
setError(null);
69+
setSucceeded(true);
70+
setProcessing(false);
71+
setMetadata(payload.paymentIntent);
72+
console.log("[PaymentIntent]", payload.paymentIntent);
73+
}
74+
};
75+
76+
77+
const renderSuccess = () => {
78+
return (
79+
<div className="sr-field-success message">
80+
<h1>Your test payment succeeded</h1>
81+
<p>View PaymentIntent response:</p>
82+
<pre className="sr-callout">
83+
<code>{JSON.stringify(metadata, null, 2)}</code>
84+
</pre>
85+
</div>
86+
);
87+
};
88+
89+
const renderForm = () => {
90+
const options = {
91+
style: {
92+
base: {
93+
color: "#32325d",
94+
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
95+
fontSmoothing: "antialiased",
96+
fontSize: "16px",
97+
"::placeholder": {
98+
color: "#aab7c4"
99+
}
100+
},
101+
invalid: {
102+
color: "#fa755a",
103+
iconColor: "#fa755a"
104+
}
105+
}
106+
};
107+
108+
return (
109+
<div>
110+
<form onSubmit={handleSubmit}>
111+
<h1>
112+
{currency.toLocaleUpperCase()}{" "}
113+
{/* {amount} */}
114+
{/* {userLocale} */}
115+
{/* replace navigator.language w/ userLocale, that way numbers are in the right language */}
116+
{amount.toLocaleString(userLocale, {
117+
minimumFractionDigits: 2
118+
})}{" "}
119+
</h1>
120+
<h4>Pre-order the Pasha package</h4>
121+
122+
<div className="sr-combo-inputs">
123+
<div className="sr-combo-inputs-row">
124+
<input
125+
type="text"
126+
id="name"
127+
name="name"
128+
placeholder="Name"
129+
autoComplete="cardholder"
130+
className="sr-input"
131+
/>
132+
</div>
133+
134+
<div className="sr-combo-inputs-row">
135+
<CardElement
136+
className="sr-input sr-card-element"
137+
options={options}
138+
/>
139+
</div>
140+
</div>
141+
142+
{error && <div className="message sr-field-error">{error}</div>}
143+
144+
<button
145+
className="btn"
146+
disabled={processing || !clientSecret || !stripe}
147+
>
148+
{processing ? "Processing…" : "Pay"}
149+
</button>
150+
</form>
151+
<style jsx global>{`
152+
.sr-combo-inputs {
153+
margin: 20px 0;
154+
}
155+
156+
.sr-input {
157+
font-size: 16px;
158+
}
159+
160+
.sr-card-element {
161+
padding-top: 12px;
162+
}
163+
164+
.btn {
165+
font-size: 16px;
166+
}
167+
`}</style>
168+
</div>
169+
);
170+
};
171+
172+
return (
173+
<div className="checkout-form">
174+
<div className="sr-payment-form">
175+
<div className="sr-form-row" />
176+
{succeeded ? renderSuccess() : renderForm()}
177+
</div>
178+
</div>
179+
);
180+
181+
}

components/Layout.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Head from 'next/head'
2+
import Navbar from './Navbar.js'
3+
// import Footer from './Footer.js'
4+
5+
function Layout(props) {
6+
return (
7+
<div>
8+
<Head>
9+
<title>Shopping Cart</title>
10+
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
11+
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossOrigin="anonymous"/>
12+
</Head>
13+
<Navbar/>
14+
<div className="container-fluid">{props.children}</div>
15+
{/* <Footer/> */}
16+
</div>
17+
)
18+
}
19+
export default Layout

components/Navbar.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import { useContext } from 'react';
3+
import CartContext from './cartContext';
4+
import Link from 'next/link';
5+
6+
const Navbar = (props) => {
7+
const { cart } = useContext(CartContext);
8+
return (
9+
<nav className="navbar navbar-light bg-light fixed-top">
10+
<h3><Link href="/">Summit</Link>Chasing</h3>
11+
<a href="/cart" className="btn btn-outline-primary my-2 my-sm-0">Cart {cart.length}</a>
12+
</nav>
13+
);
14+
};
15+
export default Navbar;

components/cartContext.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {createContext} from 'react';
2+
3+
const CartContext = createContext();
4+
5+
export default CartContext;

lib/api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,4 @@ export async function getAllProductsForHome(preview) {
6363
'image': defaultProductVariant.images[0].asset->url,
6464
'images': defaultProductVariant.images,
6565
'price': defaultProductVariant.price,
66-
`
66+
`

lib/database.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import createClient from 'serverless-pg';
2+
3+
const dbClient = createClient({
4+
config: {
5+
host: process.env.HOST,
6+
port: process.env.PORT,
7+
user: process.env.USERNAME,
8+
password: process.env.PASSWORD,
9+
database: process.env.NAME,
10+
},
11+
onConnect: () => {console.log('connected to database')},
12+
onClose: () => {console.log('disconnected from database')},
13+
beforeQuery: () => {},
14+
afterQuery: () => {},
15+
});
16+
17+
export default dbClient;

0 commit comments

Comments
 (0)