Run 'format'

This commit is contained in:
2020-11-13 16:24:00 -06:00
parent 0190b0a30e
commit 313ef146dd
7 changed files with 409 additions and 381 deletions

View File

@@ -2,10 +2,10 @@
* Global Config * Global Config
*/ */
const config = {} const config = {};
// Domains // Domains
config.domains = {} config.domains = {};
/** /**
* API Domain * API Domain
@@ -13,6 +13,6 @@ config.domains = {}
* This will enable your front-end to communicate with your back-end. * This will enable your front-end to communicate with your back-end.
* (e.g. 'https://api.mydomain.com' or 'https://091jafsl10.execute-api.us-east-1.amazonaws.com') * (e.g. 'https://api.mydomain.com' or 'https://091jafsl10.execute-api.us-east-1.amazonaws.com')
*/ */
config.domains.api = 'https://13gtp0hrak.execute-api.us-east-1.amazonaws.com' config.domains.api = "https://13gtp0hrak.execute-api.us-east-1.amazonaws.com";
export default config export default config;

View File

@@ -1,247 +1,284 @@
import React, { Component } from 'react' import React, { Component } from "react";
import { import { Link } from "gatsby";
Link import styles from "./Auth.module.css";
} from 'gatsby' import { userRegister, userLogin, userGet, saveSession } from "../../utils";
import styles from './Auth.module.css'
import {
userRegister,
userLogin,
userGet,
saveSession,
} from '../../utils'
class Auth extends Component { class Auth extends Component {
constructor(props) {
super(props);
constructor(props) { const pathName = window.location.pathname.replace("/", "");
super(props)
const pathName = window.location.pathname.replace('/', '') this.state = {};
this.state.state = pathName;
this.state.loading = true;
this.state.error = null;
this.state.formEmail = "";
this.state.formPassword = "";
this.state = {} // Bindings
this.state.state = pathName this.handleFormInput = this.handleFormInput.bind(this);
this.state.loading = true this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.state.error = null this.handleFormTypeChange = this.handleFormTypeChange.bind(this);
this.state.formEmail = '' }
this.state.formPassword = ''
// Bindings /**
this.handleFormInput = this.handleFormInput.bind(this) * Component did mount
this.handleFormSubmit = this.handleFormSubmit.bind(this) */
this.handleFormTypeChange = this.handleFormTypeChange.bind(this) componentDidMount() {
} this.setState({
loading: false,
});
/** // Clear query params
* Component did mount const url = document.location.href;
*/ window.history.pushState({}, "", url.split("?")[0]);
componentDidMount() { }
this.setState({
loading: false
})
// Clear query params /**
const url = document.location.href * Handles a form change
window.history.pushState({}, '', url.split('?')[0]) */
} handleFormTypeChange(type) {
this.setState({ state: type }, () => {
this.props.history.push(`/${type}`);
});
}
/** /**
* Handles a form change * Handle text changes within form fields
*/ */
handleFormTypeChange(type) { handleFormInput(field, value) {
this.setState({ state: type }, value = value.trim();
() => {
this.props.history.push(`/${type}`)
})
}
/** const nextState = {};
* Handle text changes within form fields nextState[field] = value;
*/
handleFormInput(field, value) {
value = value.trim()
const nextState = {} this.setState(Object.assign(this.state, nextState));
nextState[field] = value }
this.setState(Object.assign(this.state, nextState)) /**
} * Handles form submission
* @param {object} evt
*/
async handleFormSubmit(evt) {
evt.preventDefault();
/** this.setState({ loading: true });
* Handles form submission
* @param {object} evt
*/
async handleFormSubmit(evt) {
evt.preventDefault()
this.setState({ loading: true }) // Validate email
if (!this.state.formEmail) {
return this.setState({
loading: false,
formError: "email is required",
});
}
// Validate email // Validate password
if (!this.state.formEmail) { if (!this.state.formPassword) {
return this.setState({ return this.setState({
loading: false, loading: false,
formError: 'email is required' formError: "password is required",
}) });
} }
// Validate password let token;
if (!this.state.formPassword) { try {
return this.setState({ if (this.state.state === "register") {
loading: false, token = await userRegister(
formError: 'password is required' this.state.formEmail,
}) this.state.formPassword
} );
} else {
token = await userLogin(
this.state.formEmail,
this.state.formPassword
);
}
} catch (error) {
console.log(error);
if (error.message) {
this.setState({
formError: error.message,
loading: false,
});
} else {
this.setState({
formError:
"Sorry, something unknown went wrong. Please try again.",
loading: false,
});
}
return;
}
let token // Fetch user record and set session in cookie
try { let user = await userGet(token.token);
if (this.state.state === 'register') { user = user.user;
token = await userRegister(this.state.formEmail, this.state.formPassword) saveSession(user.id, user.email, token.token);
} else {
token = await userLogin(this.state.formEmail, this.state.formPassword)
}
} catch (error) {
console.log(error)
if (error.message) {
this.setState({
formError: error.message,
loading: false
})
} else {
this.setState({
formError: 'Sorry, something unknown went wrong. Please try again.',
loading: false
})
}
return
}
// Fetch user record and set session in cookie window.location.replace("/");
let user = await userGet(token.token) }
user = user.user
saveSession(user.id, user.email, token.token)
window.location.replace('/') render() {
} return (
<div className={`${styles.container} animateFadeIn`}>
<div className={styles.containerInner}>
{/* Logo */}
render() { <Link to="/" className={`${styles.logo}`}>
<img
draggable="false"
src={"./fullstack-app-title.png"}
alt="serverless-fullstack-application"
/>
</Link>
return ( {/* Loading */}
<div className={`${styles.container} animateFadeIn`}>
<div className={styles.containerInner}>
{ /* Logo */} {this.state.loading && <div>loading, man!</div>}
<Link to='/' className={`${styles.logo}`}> {/* Registration Form */}
<img
draggable='false'
src={'./fullstack-app-title.png'}
alt='serverless-fullstack-application'
/>
</Link>
{ /* Loading */} {!this.state.loading && (
<div className={styles.formType}>
<div
className={`${styles.formTypeRegister}
${
this.state.state === "register" ? styles.formTypeActive : ""
}`}
onClick={(e) => {
this.handleFormTypeChange("register");
}}
>
Register
</div>
<div
className={`${styles.formTypeSignIn}
${this.state.state === "login" ? styles.formTypeActive : ""}`}
onClick={(e) => {
this.handleFormTypeChange("login");
}}
>
Sign-In
</div>
</div>
)}
{this.state.loading && ( {this.state.state === "register" && !this.state.loading && (
<div> <div className={styles.containerRegister}>
loading, man! <form
</div> className={styles.form}
)} onSubmit={this.handleFormSubmit}
>
<div className={styles.formField}>
<label className={styles.formLabel}>
email
</label>
<input
type="text"
placeholder="yours@example.com"
className={styles.formInput}
value={this.state.formEmail}
onChange={(e) => {
this.handleFormInput(
"formEmail",
e.target.value
);
}}
/>
</div>
<div className={styles.formField}>
<label className={styles.formLabel}>
password
</label>
<input
type="password"
placeholder="your password"
className={styles.formInput}
value={this.state.formPassword}
onChange={(e) => {
this.handleFormInput(
"formPassword",
e.target.value
);
}}
/>
</div>
{ /* Registration Form */} {this.state.formError && (
<div className={styles.formError}>
{this.state.formError}
</div>
)}
{!this.state.loading && ( <input
<div className={styles.formType}> className={`buttonPrimaryLarge ${styles.formButton}`}
<div type="submit"
className={ value="Register"
`${styles.formTypeRegister} />
${this.state.state === 'register' ? styles.formTypeActive : ''}`} </form>
onClick={(e) => { this.handleFormTypeChange('register') }}> </div>
Register )}
</div>
<div
className={
`${styles.formTypeSignIn}
${this.state.state === 'login' ? styles.formTypeActive : ''}`}
onClick={(e) => { this.handleFormTypeChange('login') }}>
Sign-In
</div>
</div>
)}
{this.state.state === 'register' && !this.state.loading && ( {this.state.state === "login" && !this.state.loading && (
<div className={styles.containerRegister}> <div className={styles.containerSignIn}>
<form
className={styles.form}
onSubmit={this.handleFormSubmit}
>
<div className={styles.formField}>
<label className={styles.formLabel}>
email
</label>
<input
type="text"
placeholder="yours@example.com"
className={styles.formInput}
value={this.state.formEmail}
onChange={(e) => {
this.handleFormInput(
"formEmail",
e.target.value
);
}}
/>
</div>
<div className={styles.formField}>
<label className={styles.formLabel}>
password
</label>
<input
type="password"
placeholder="your password"
className={styles.formInput}
value={this.state.formPassword}
onChange={(e) => {
this.handleFormInput(
"formPassword",
e.target.value
);
}}
/>
</div>
<form className={styles.form} onSubmit={this.handleFormSubmit}> {this.state.formError && (
<div className={styles.formField}> <div className={styles.formError}>
<label className={styles.formLabel}>email</label> {this.state.formError}
<input </div>
type='text' )}
placeholder='yours@example.com'
className={styles.formInput}
value={this.state.formEmail}
onChange={(e) => { this.handleFormInput('formEmail', e.target.value) }}
/>
</div>
<div className={styles.formField}>
<label className={styles.formLabel}>password</label>
<input
type='password'
placeholder='your password'
className={styles.formInput}
value={this.state.formPassword}
onChange={(e) => { this.handleFormInput('formPassword', e.target.value) }}
/>
</div>
{this.state.formError && ( <input
<div className={styles.formError}>{this.state.formError}</div> className={`buttonPrimaryLarge ${styles.formButton}`}
)} type="submit"
value="Sign In"
<input />
className={`buttonPrimaryLarge ${styles.formButton}`} </form>
type='submit' </div>
value='Register' )}
/> </div>
</div>
</form> );
</div> }
)}
{this.state.state === 'login' && !this.state.loading && (
<div className={styles.containerSignIn}>
<form className={styles.form} onSubmit={this.handleFormSubmit}>
<div className={styles.formField}>
<label className={styles.formLabel}>email</label>
<input
type='text'
placeholder='yours@example.com'
className={styles.formInput}
value={this.state.formEmail}
onChange={(e) => { this.handleFormInput('formEmail', e.target.value) }}
/>
</div>
<div className={styles.formField}>
<label className={styles.formLabel}>password</label>
<input
type='password'
placeholder='your password'
className={styles.formInput}
value={this.state.formPassword}
onChange={(e) => { this.handleFormInput('formPassword', e.target.value) }}
/>
</div>
{this.state.formError && (
<div className={styles.formError}>{this.state.formError}</div>
)}
<input className={`buttonPrimaryLarge ${styles.formButton}`} type='submit' value='Sign In' />
</form>
</div>
)}
</div>
</div>
)
}
} }
export default Auth export default Auth;

View File

@@ -1,82 +1,69 @@
import React, { Component } from 'react' import React, { Component } from "react";
import { import { withRouter } from "react-router-dom";
withRouter import styles from "./Dashboard.module.css";
} from 'react-router-dom' import { getSession, deleteSession } from "../../utils";
import styles from './Dashboard.module.css'
import {
getSession,
deleteSession
} from '../../utils'
class Dashboard extends Component { class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {};
constructor(props) { // Bindings
super(props) this.logout = this.logout.bind(this);
this.state = {} }
// Bindings async componentDidMount() {
this.logout = this.logout.bind(this) const userSession = getSession();
}
async componentDidMount() { this.setState({
session: userSession,
});
}
const userSession = getSession() /**
* Log user out by clearing cookie and redirecting
*/
logout() {
deleteSession();
this.props.history.push(`/`);
}
this.setState({ render() {
session: userSession, return (
}) <div className={`${styles.container} animateFadeIn`}>
} <div className={styles.containerInner}>
{/* Navigation */}
/** <div className={styles.navigationContainer}>
* Log user out by clearing cookie and redirecting <div className={`link`}>
*/ {this.state.session
logout() { ? this.state.session.userEmail
deleteSession() : ""}
this.props.history.push(`/`) </div>
} <div className={`link`} onClick={this.logout}>
logout
</div>
</div>
render() { {/* Content */}
return ( <div className={`${styles.contentContainer}`}>
<div className={`${styles.container} animateFadeIn`}> <div className={`${styles.artwork} animateFlicker`}>
<div className={styles.containerInner}> <img
draggable="false"
src={"./fullstack-app-artwork.png"}
alt="serverless-fullstack-application"
/>
</div>
{ /* Navigation */ } <div className={`${styles.welcomeMessage}`}>
Welcome to your serverless fullstack dashboard...
<div className={styles.navigationContainer}> </div>
<div </div>
className={`link`}> </div>
{ this.state.session ? this.state.session.userEmail : '' } </div>
</div> );
<div }
className={`link`}
onClick={this.logout}>
logout
</div>
</div>
{ /* Content */ }
<div className={`${styles.contentContainer}`}>
<div className={`${styles.artwork} animateFlicker`}>
<img
draggable='false'
src={'./fullstack-app-artwork.png'}
alt='serverless-fullstack-application'
/>
</div>
<div className={`${styles.welcomeMessage}`}>
Welcome to your serverless fullstack dashboard...
</div>
</div>
</div>
</div>
)
}
} }
export default withRouter(Dashboard) export default withRouter(Dashboard);

View File

@@ -3,9 +3,9 @@ import Auth from "../components/Auth/Auth";
export default function Home() { export default function Home() {
return ( return (
<div> <div>
<p>Hello world!</p> <p>Hello world!</p>
<Auth/> <Auth />
</div> </div>
); );
} }

View File

@@ -2,70 +2,69 @@
* Utils: Back-end * Utils: Back-end
*/ */
import config from '../../config' import config from "../../config";
/** /**
* Register a new user * Register a new user
*/ */
export const userRegister = async (email, password) => { export const userRegister = async (email, password) => {
return await requestApi('/users/register', 'POST', { email, password }) return await requestApi("/users/register", "POST", { email, password });
} };
/** /**
* Login a new user * Login a new user
*/ */
export const userLogin = async (email, password) => { export const userLogin = async (email, password) => {
return await requestApi('/users/login', 'POST', { email, password }) return await requestApi("/users/login", "POST", { email, password });
} };
/** /**
* userGet * userGet
*/ */
export const userGet = async (token) => { export const userGet = async (token) => {
return await requestApi('/user', 'POST', null, { return await requestApi("/user", "POST", null, {
Authorization: `Bearer ${token}` Authorization: `Bearer ${token}`,
}) });
} };
/** /**
* API request to call the backend * API request to call the backend
*/ */
export const requestApi = async ( export const requestApi = async (
path = '', path = "",
method = 'GET', method = "GET",
data = null, data = null,
headers = {}) => { headers = {}
) => {
// Check if API URL has been set
if (!config?.domains?.api) {
throw new Error(
`Error: Missing API Domain Please add the API domain from your serverless Express.js back-end to this front-end application. You can do this in the "site" folder, in the "./config.js" file. Instructions are listed there and in the documentation.`
);
}
// Check if API URL has been set // Prepare URL
if (!config?.domains?.api) { if (!path.startsWith("/")) {
throw new Error(`Error: Missing API Domain Please add the API domain from your serverless Express.js back-end to this front-end application. You can do this in the "site" folder, in the "./config.js" file. Instructions are listed there and in the documentation.`) path = `/${path}`;
} }
const url = `${config.domains.api}${path}`;
// Prepare URL // Set headers
if (!path.startsWith('/')) { headers = Object.assign({ "Content-Type": "application/json" }, headers);
path = `/${path}`
}
const url = `${config.domains.api}${path}`
// Set headers // Default options are marked with *
headers = Object.assign( const response = await fetch(url, {
{ 'Content-Type': 'application/json' }, method: method.toUpperCase(),
headers mode: "cors",
) cache: "no-cache",
headers,
body: data ? JSON.stringify(data) : null,
});
// Default options are marked with * if (response.status < 200 || response.status >= 300) {
const response = await fetch(url, { const error = await response.json();
method: method.toUpperCase(), throw new Error(error.error);
mode: 'cors', }
cache: 'no-cache',
headers,
body: data ? JSON.stringify(data) : null
})
if (response.status < 200 || response.status >= 300) { return await response.json();
const error = await response.json() };
throw new Error(error.error)
}
return await response.json()
}

View File

@@ -1,76 +1,81 @@
import Cookies from 'js-cookie' import Cookies from "js-cookie";
/** /**
* Format Org and Username correctly for the Serverless Platform backend * Format Org and Username correctly for the Serverless Platform backend
*/ */
export const formatOrgAndUsername = (name = '') => { export const formatOrgAndUsername = (name = "") => {
name = name.toString().toLowerCase().replace(/[^a-z\d-]+/gi, '-') name = name
// Remove multiple instances of hyphens .toString()
name = name.replace(/-{2,}/g, '-') .toLowerCase()
if (name.length > 40) { .replace(/[^a-z\d-]+/gi, "-");
name = name.substring(0, 40) // Remove multiple instances of hyphens
} name = name.replace(/-{2,}/g, "-");
return name if (name.length > 40) {
} name = name.substring(0, 40);
}
return name;
};
/** /**
* Parse query parameters in a URL * Parse query parameters in a URL
* @param {*} searchString * @param {*} searchString
*/ */
export const parseQueryParams = (searchString = null) => { export const parseQueryParams = (searchString = null) => {
if (!searchString) { if (!searchString) {
return null return null;
} }
// Clone string // Clone string
let clonedParams = (' ' + searchString).slice(1) let clonedParams = (" " + searchString).slice(1);
return clonedParams return clonedParams
.substr(1) .substr(1)
.split('&') .split("&")
.filter((el) => el.length) .filter((el) => el.length)
.map((el) => el.split('=')) .map((el) => el.split("="))
.reduce( .reduce(
(accumulator, currentValue) => (accumulator, currentValue) =>
Object.assign(accumulator, { Object.assign(accumulator, {
[decodeURIComponent(currentValue.shift())]: decodeURIComponent(currentValue.pop()) [decodeURIComponent(
}), currentValue.shift()
{} )]: decodeURIComponent(currentValue.pop()),
) }),
} {}
);
};
/** /**
* Parse hash fragment parameters in a URL * Parse hash fragment parameters in a URL
*/ */
export const parseHashFragment = (hashString) => { export const parseHashFragment = (hashString) => {
const hashData = {} const hashData = {};
let hash = decodeURI(hashString) let hash = decodeURI(hashString);
hash = hash.split('&') hash = hash.split("&");
hash.forEach((val) => { hash.forEach((val) => {
val = val.replace('#', '') val = val.replace("#", "");
hashData[val.split('=')[0]] = val.split('=')[1] hashData[val.split("=")[0]] = val.split("=")[1];
}) });
return hashData return hashData;
} };
/** /**
* Save session in browser cookie * Save session in browser cookie
*/ */
export const saveSession = (userId, userEmail, userToken) => { export const saveSession = (userId, userEmail, userToken) => {
Cookies.set('serverless', { userId, userEmail, userToken }) Cookies.set("serverless", { userId, userEmail, userToken });
} };
/** /**
* Get session in browser cookie * Get session in browser cookie
*/ */
export const getSession = () => { export const getSession = () => {
const data = Cookies.get('serverless') const data = Cookies.get("serverless");
return data ? JSON.parse(data) : null return data ? JSON.parse(data) : null;
} };
/** /**
* Delete session in browser cookie * Delete session in browser cookie
*/ */
export const deleteSession = () => { export const deleteSession = () => {
Cookies.remove('serverless') Cookies.remove("serverless");
} };

View File

@@ -1,2 +1,2 @@
export * from './helpers' export * from "./helpers";
export * from './api' export * from "./api";