Skip to content

Commit 9610325

Browse files
committed
Building our React app
1 parent 8e31e7c commit 9610325

File tree

19 files changed

+558
-31
lines changed

19 files changed

+558
-31
lines changed

package-lock.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"react-dom": "^16.9.0",
1010
"react-router-bootstrap": "^0.25.0",
1111
"react-router-dom": "^5.0.1",
12-
"react-scripts": "3.1.2"
12+
"react-scripts": "3.1.2",
13+
"react-stripe-elements": "^5.0.1"
1314
},
1415
"scripts": {
1516
"start": "react-scripts start",

public/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
Learn how to configure a non-root public URL by running `npm run build`.
2727
-->
2828
<title>Scratch - A simple note taking app</title>
29+
30+
<script src="https://js.stripe.com/v3/"></script>
2931
</head>
3032
<body>
3133
<noscript>You need to enable JavaScript to run this app.</noscript>

src/App.js

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,33 +37,40 @@ function App(props) {
3737
}
3838

3939
return (
40-
!isAuthenticating &&
41-
<div className="App container">
42-
<Navbar fluid collapseOnSelect>
43-
<Navbar.Header>
44-
<Navbar.Brand>
45-
<Link to="/">Scratch</Link>
46-
</Navbar.Brand>
47-
<Navbar.Toggle />
48-
</Navbar.Header>
49-
<Navbar.Collapse>
50-
<Nav pullRight>
51-
{isAuthenticated
52-
? <NavItem onClick={handleLogout}>Logout</NavItem>
53-
: <>
40+
!isAuthenticating && (
41+
<div className="App container">
42+
<Navbar fluid collapseOnSelect>
43+
<Navbar.Header>
44+
<Navbar.Brand>
45+
<Link to="/">Scratch</Link>
46+
</Navbar.Brand>
47+
<Navbar.Toggle />
48+
</Navbar.Header>
49+
<Navbar.Collapse>
50+
<Nav pullRight>
51+
{isAuthenticated ? (
52+
<>
53+
<LinkContainer to="/settings">
54+
<NavItem>Settings</NavItem>
55+
</LinkContainer>
56+
<NavItem onClick={handleLogout}>Logout</NavItem>
57+
</>
58+
) : (
59+
<>
5460
<LinkContainer to="/signup">
5561
<NavItem>Signup</NavItem>
5662
</LinkContainer>
5763
<LinkContainer to="/login">
5864
<NavItem>Login</NavItem>
5965
</LinkContainer>
6066
</>
61-
}
62-
</Nav>
63-
</Navbar.Collapse>
64-
</Navbar>
65-
<Routes appProps={{ isAuthenticated, userHasAuthenticated }} />
66-
</div>
67+
)}
68+
</Nav>
69+
</Navbar.Collapse>
70+
</Navbar>
71+
<Routes appProps={{ isAuthenticated, userHasAuthenticated }} />
72+
</div>
73+
)
6774
);
6875
}
6976

src/Routes.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
import React from "react";
22
import { Route, Switch } from "react-router-dom";
33
import AppliedRoute from "./components/AppliedRoute";
4+
import AuthenticatedRoute from "./components/AuthenticatedRoute";
5+
import UnauthenticatedRoute from "./components/UnauthenticatedRoute";
6+
47
import Home from "./containers/Home";
58
import Login from "./containers/Login";
9+
import Notes from "./containers/Notes";
610
import Signup from "./containers/Signup";
11+
import NewNote from "./containers/NewNote";
12+
import Settings from "./containers/Settings";
713
import NotFound from "./containers/NotFound";
814

915
export default function Routes({ appProps }) {
1016
return (
1117
<Switch>
1218
<AppliedRoute path="/" exact component={Home} appProps={appProps} />
13-
<AppliedRoute path="/login" exact component={Login} appProps={appProps} />
14-
<AppliedRoute path="/signup" exact component={Signup} appProps={appProps} />
15-
{ /* Finally, catch all unmatched routes */ }
19+
<UnauthenticatedRoute path="/login" exact component={Login} appProps={appProps} />
20+
<UnauthenticatedRoute path="/signup" exact component={Signup} appProps={appProps} />
21+
<AuthenticatedRoute path="/settings" exact component={Settings} appProps={appProps} />
22+
<AuthenticatedRoute path="/notes/new" exact component={NewNote} appProps={appProps} />
23+
<AuthenticatedRoute path="/notes/:id" exact component={Notes} appProps={appProps} />
24+
{/* Finally, catch all unmatched routes */}
1625
<Route component={NotFound} />
1726
</Switch>
1827
);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from "react";
2+
import { Route, Redirect } from "react-router-dom";
3+
4+
export default function AuthenticatedRoute({ component: C, appProps, ...rest }) {
5+
return (
6+
<Route
7+
{...rest}
8+
render={props =>
9+
appProps.isAuthenticated
10+
? <C {...props} {...appProps} />
11+
: <Redirect
12+
to={`/login?redirect=${props.location.pathname}${props.location
13+
.search}`}
14+
/>}
15+
/>
16+
);
17+
}

src/components/BillingForm.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.BillingForm .card-field {
2+
margin-bottom: 15px;
3+
background-color: white;
4+
padding: 11px 16px;
5+
border-radius: 6px;
6+
border: 1px solid #CCC;
7+
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
8+
line-height: 1.3333333;
9+
}
10+
11+
.BillingForm .card-field.StripeElement--focus {
12+
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
13+
border-color: #66AFE9;
14+
}

src/components/BillingForm.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, { useState } from "react";
2+
import { FormGroup, FormControl, ControlLabel } from "react-bootstrap";
3+
import { CardElement, injectStripe } from "react-stripe-elements";
4+
import LoaderButton from "./LoaderButton";
5+
import { useFormFields } from "../libs/hooksLib";
6+
import "./BillingForm.css";
7+
8+
function BillingForm({ isLoading, onSubmit, ...props }) {
9+
const [fields, handleFieldChange] = useFormFields({
10+
name: "",
11+
storage: ""
12+
});
13+
const [isProcessing, setIsProcessing] = useState(false);
14+
const [isCardComplete, setIsCardComplete] = useState(false);
15+
16+
isLoading = isProcessing || isLoading;
17+
18+
function validateForm() {
19+
return (
20+
fields.name !== "" &&
21+
fields.storage !== "" &&
22+
isCardComplete
23+
);
24+
}
25+
26+
async function handleSubmitClick(event) {
27+
event.preventDefault();
28+
29+
setIsProcessing(true);
30+
31+
const { token, error } = await props.stripe.createToken({ name: fields.name });
32+
33+
setIsProcessing(false);
34+
35+
onSubmit(fields.storage, { token, error });
36+
}
37+
38+
return (
39+
<form className="BillingForm" onSubmit={handleSubmitClick}>
40+
<FormGroup bsSize="large" controlId="storage">
41+
<ControlLabel>Storage</ControlLabel>
42+
<FormControl
43+
min="0"
44+
type="number"
45+
value={fields.storage}
46+
onChange={handleFieldChange}
47+
placeholder="Number of notes to store"
48+
/>
49+
</FormGroup>
50+
<hr />
51+
<FormGroup bsSize="large" controlId="name">
52+
<ControlLabel>Cardholder&apos;s name</ControlLabel>
53+
<FormControl
54+
type="text"
55+
value={fields.name}
56+
onChange={handleFieldChange}
57+
placeholder="Name on the card"
58+
/>
59+
</FormGroup>
60+
<ControlLabel>Credit Card Info</ControlLabel>
61+
<CardElement
62+
className="card-field"
63+
onChange={e => setIsCardComplete(e.complete)}
64+
style={{
65+
base: { fontSize: "18px", fontFamily: '"Open Sans", sans-serif' }
66+
}}
67+
/>
68+
<LoaderButton
69+
block
70+
type="submit"
71+
bsSize="large"
72+
isLoading={isLoading}
73+
disabled={!validateForm()}
74+
>
75+
Purchase
76+
</LoaderButton>
77+
</form>
78+
);
79+
}
80+
81+
export default injectStripe(BillingForm);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from "react";
2+
import { Route, Redirect } from "react-router-dom";
3+
4+
function querystring(name, url = window.location.href) {
5+
name = name.replace(/[[]]/g, "\\$&");
6+
7+
const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)", "i");
8+
const results = regex.exec(url);
9+
10+
if (!results) {
11+
return null;
12+
}
13+
if (!results[2]) {
14+
return "";
15+
}
16+
17+
return decodeURIComponent(results[2].replace(/\+/g, " "));
18+
}
19+
20+
export default function UnauthenticatedRoute({ component: C, appProps, ...rest }) {
21+
const redirect = querystring("redirect");
22+
return (
23+
<Route
24+
{...rest}
25+
render={props =>
26+
!appProps.isAuthenticated
27+
? <C {...props} {...appProps} />
28+
: <Redirect
29+
to={redirect === "" || redirect === null ? "/" : redirect}
30+
/>}
31+
/>
32+
);
33+
}

src/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export default {
2+
MAX_ATTACHMENT_SIZE: 5000000,
3+
STRIPE_KEY: "pk_test_v1amvR35uoCNduJfkqGB8RLD",
24
s3: {
35
REGION: "us-east-1",
46
BUCKET: "notes-app-uploads"

0 commit comments

Comments
 (0)