Skip to content

Commit 26e950e

Browse files
committed
Building our React app
1 parent 8379917 commit 26e950e

29 files changed

+2233
-182
lines changed

package-lock.json

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

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"@aws-amplify/core": "^3.2.2",
6+
"aws-amplify": "^3.0.6",
77
"react": "^16.13.1",
88
"react-bootstrap": "^0.33.1",
99
"react-dom": "^16.13.1",
1010
"react-router-bootstrap": "^0.25.0",
1111
"react-router-dom": "^5.1.2",
12-
"react-scripts": "3.4.1"
12+
"react-scripts": "3.4.1",
13+
"react-stripe-elements": "^6.1.1"
1314
},
1415
"scripts": {
1516
"start": "react-scripts start",

public/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
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+
<script src="https://js.stripe.com/v3/"></script>
2930
</head>
3031
<body>
3132
<noscript>You need to enable JavaScript to run this app.</noscript>

src/App.js

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,83 @@
1-
import React from "react";
2-
import { Link } from "react-router-dom";
1+
import React, { useState, useEffect } from "react";
2+
import { Auth } from "aws-amplify";
3+
import { Link, useHistory } from "react-router-dom";
34
import { Nav, Navbar, NavItem } from "react-bootstrap";
45
import { LinkContainer } from "react-router-bootstrap";
6+
import { AppContext } from "./libs/contextLib";
7+
import { onError } from "./libs/errorLib";
58
import Routes from "./Routes";
69
import "./App.css";
710

811
function App() {
12+
const history = useHistory();
13+
const [isAuthenticating, setIsAuthenticating] = useState(true);
14+
const [isAuthenticated, userHasAuthenticated] = useState(false);
15+
16+
useEffect(() => {
17+
onLoad();
18+
}, []);
19+
20+
async function onLoad() {
21+
try {
22+
await Auth.currentSession();
23+
userHasAuthenticated(true);
24+
}
25+
catch(e) {
26+
if (e !== 'No current user') {
27+
onError(e);
28+
}
29+
}
30+
31+
setIsAuthenticating(false);
32+
}
33+
34+
async function handleLogout() {
35+
await Auth.signOut();
36+
37+
userHasAuthenticated(false);
38+
39+
history.push("/login");
40+
}
41+
942
return (
10-
<div className="App container">
11-
<Navbar fluid collapseOnSelect>
12-
<Navbar.Header>
13-
<Navbar.Brand>
14-
<Link to="/">Scratch</Link>
15-
</Navbar.Brand>
16-
<Navbar.Toggle />
17-
</Navbar.Header>
18-
<Navbar.Collapse>
19-
<Nav pullRight>
20-
<LinkContainer to="/signup">
21-
<NavItem>Signup</NavItem>
22-
</LinkContainer>
23-
<LinkContainer to="/login">
24-
<NavItem>Login</NavItem>
25-
</LinkContainer>
26-
</Nav>
27-
</Navbar.Collapse>
28-
</Navbar>
29-
<Routes />
30-
</div>
43+
!isAuthenticating && (
44+
<div className="App container">
45+
<Navbar fluid collapseOnSelect>
46+
<Navbar.Header>
47+
<Navbar.Brand>
48+
<Link to="/">Scratch</Link>
49+
</Navbar.Brand>
50+
<Navbar.Toggle />
51+
</Navbar.Header>
52+
<Navbar.Collapse>
53+
<Nav pullRight>
54+
{isAuthenticated ? (
55+
<>
56+
<LinkContainer to="/settings">
57+
<NavItem>Settings</NavItem>
58+
</LinkContainer>
59+
<NavItem onClick={handleLogout}>Logout</NavItem>
60+
</>
61+
) : (
62+
<>
63+
<LinkContainer to="/signup">
64+
<NavItem>Signup</NavItem>
65+
</LinkContainer>
66+
<LinkContainer to="/login">
67+
<NavItem>Login</NavItem>
68+
</LinkContainer>
69+
</>
70+
)}
71+
</Nav>
72+
</Navbar.Collapse>
73+
</Navbar>
74+
<AppContext.Provider
75+
value={{ isAuthenticated, userHasAuthenticated }}
76+
>
77+
<Routes />
78+
</AppContext.Provider>
79+
</div>
80+
)
3181
);
3282
}
3383

src/Routes.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import React from "react";
22
import { Route, Switch } from "react-router-dom";
33

4+
import AuthenticatedRoute from "./components/AuthenticatedRoute";
5+
import UnauthenticatedRoute from "./components/UnauthenticatedRoute";
6+
47
import Home from "./containers/Home";
8+
import Login from "./containers/Login";
9+
import Notes from "./containers/Notes";
10+
import Signup from "./containers/Signup";
11+
import NewNote from "./containers/NewNote";
12+
import Settings from "./containers/Settings";
513
import NotFound from "./containers/NotFound";
614

715
export default function Routes() {
@@ -10,6 +18,21 @@ export default function Routes() {
1018
<Route exact path="/">
1119
<Home />
1220
</Route>
21+
<UnauthenticatedRoute exact path="/login">
22+
<Login />
23+
</UnauthenticatedRoute>
24+
<UnauthenticatedRoute exact path="/signup">
25+
<Signup />
26+
</UnauthenticatedRoute>
27+
<AuthenticatedRoute exact path="/settings">
28+
<Settings />
29+
</AuthenticatedRoute>
30+
<AuthenticatedRoute exact path="/notes/new">
31+
<NewNote />
32+
</AuthenticatedRoute>
33+
<AuthenticatedRoute exact path="/notes/:id">
34+
<Notes />
35+
</AuthenticatedRoute>
1336
{/* Finally, catch all unmatched routes */}
1437
<Route>
1538
<NotFound />
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from "react";
2+
import { Route, Redirect, useLocation } from "react-router-dom";
3+
import { useAppContext } from "../libs/contextLib";
4+
5+
export default function AuthenticatedRoute({ children, ...rest }) {
6+
const { pathname, search } = useLocation();
7+
const { isAuthenticated } = useAppContext();
8+
return (
9+
<Route {...rest}>
10+
{isAuthenticated ? (
11+
children
12+
) : (
13+
<Redirect to={
14+
`/login?redirect=${pathname}${search}`
15+
} />
16+
)}
17+
</Route>
18+
);
19+
}

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);

src/components/LoaderButton.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.LoaderButton .spinning.glyphicon {
2+
margin-right: 7px;
3+
top: 2px;
4+
animation: spin 1s infinite linear;
5+
}
6+
@keyframes spin {
7+
from { transform: scale(1) rotate(0deg); }
8+
to { transform: scale(1) rotate(360deg); }
9+
}

src/components/LoaderButton.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from "react";
2+
import { Button, Glyphicon } from "react-bootstrap";
3+
import "./LoaderButton.css";
4+
5+
export default function LoaderButton({
6+
isLoading,
7+
className = "",
8+
disabled = false,
9+
...props
10+
}) {
11+
return (
12+
<Button
13+
className={`LoaderButton ${className}`}
14+
disabled={disabled || isLoading}
15+
{...props}
16+
>
17+
{isLoading && <Glyphicon glyph="refresh" className="spinning" />}
18+
{props.children}
19+
</Button>
20+
);
21+
}

0 commit comments

Comments
 (0)