Skip to content

status icons for mailing list form including loading icon #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 10 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ import {
faPencilRuler,
faServer,
faArrowLeft,
faBook
faBook,
faSpinner,
faPaperPlane,
faCheck,
faTimes
} from "@fortawesome/free-solid-svg-icons";
import ScrollToTop from "./utility/ScrollToTop";

Expand Down Expand Up @@ -76,7 +80,11 @@ library.add(
faApple,
faArrowLeft,
faGoogleDrive,
faBook
faBook,
faSpinner,
faPaperPlane,
faCheck,
faTimes
);

const App = () => (
Expand Down
143 changes: 101 additions & 42 deletions src/components/MailingList/MailingList.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useState } from "react";
import styled from "styled-components";
import { StaticP } from "utility/ContentStyles.js";

const EmailForm = styled.form`
text-align: center;
margin: auto;
padding-bottom: 24px;
display: flex;
align-items: flex-start;
justify-content: center;
`;

const EmailInputBox = styled.input`
min-width: 250px;
border: 2px solid #555;
Expand All @@ -16,7 +19,8 @@ const EmailInputBox = styled.input`
border-bottom-left-radius: 10px;
padding: 0 16px;
`;
const EmailSubmitButton = styled.input`

const EmailSubmitButton = styled.button`
color: white;
border: 2px solid #555;
border-left: 0;
Expand All @@ -30,51 +34,106 @@ const EmailSubmitButton = styled.input`
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
&:hover {
background: rgb(222, 103, 63);
background: rgb(222, 103, 63);
}
&:disabled {
background: #aaa;
}
&:disabled:hover {
cursor: not-allowed;
}
`;

export default function MailingList() {
const [address, setAddress] = useState("");
const [result, setResult] = useState(null);
const LoadingAnimation = styled.span`
${(props) => (props.active ? "animation: spin 2s linear infinite;" : "")}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linter says semicolon is missing here. Can't see the github thing for suggesting a specific change.

display: flex;
justify-content: center;
align-items: center;
`;

async function addEmailToList(e) {
e.preventDefault();
setResult(null);
const READY_STATUS = "ready";
const LOADING_STATUS = "loading";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more idiomatic to use an object mapping to strings for enums.

const Status = {
  READY: "READY"
  ...
}

const SUCCESS_STATUS = "success";
const FAILURE_STATUS = "failure";

// make email post request
const response = await fetch('https://api.michhackers.com/email/add', {
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email: address
})
});
const statusToTitle = {};
statusToTitle[READY_STATUS] = "Send";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this part would be better if it was an object like {title: "Send", icon: "paper-plane"} to avoid that nasty nested ternary. Alternatively, move the setting of the icon to a variable and use switch instead.

statusToTitle[LOADING_STATUS] = "Sending...";
statusToTitle[SUCCESS_STATUS] = "Sent!";
statusToTitle[FAILURE_STATUS] = "Failed to send";

// handle error
if (response.status !== 200) {
setResult(`An error occured: ${response.status} ${response.statusText}`);
return;
}
export default function MailingList() {
const [address, setAddress] = useState("");
const [status, setStatus] = useState(READY_STATUS);
const [disabled, setDisabled] = useState(false);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabled state is redundant since it's only ever true when status === LOADING_STATUS

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just be like const disabled = status === LOADING_STATUS


// handle successful response
setAddress("");
setResult("Successfully added!");
}
async function addEmailToList(e) {
e.preventDefault();
setStatus(LOADING_STATUS);
setDisabled(true);
//make email post request
const response = await fetch("https://api.michhackers.com/email/add", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email: address,
}),
});

// handle error
if (response.status !== 200) {
setStatus(FAILURE_STATUS);
setDisabled(false);
console.error(
`An error occured: ${response.status} ${response.statusText}`
);
return;
}
setStatus(SUCCESS_STATUS);
setAddress("");
setDisabled(false);
}

return (
<EmailForm onSubmit={addEmailToList}>
{result && <StaticP>{result}</StaticP>}
<EmailInputBox
type="email"
value={address}
onChange={e => setAddress(e.target.value)}
placeholder="[email protected]"
required
/>
<EmailSubmitButton type="submit" value="Join" />
</EmailForm>
);
return (
<EmailForm onSubmit={addEmailToList}>
<EmailInputBox
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could use a Toast message to indicate completeness or to spit out the error message from the server.

type="email"
value={address}
onChange={(e) => {
setAddress(e.target.value);
setStatus(READY_STATUS);
setDisabled(false);
}}
placeholder="[email protected]"
required
/>
<EmailSubmitButton
type="submit"
disabled={disabled}
title={statusToTitle[status]}
>
<LoadingAnimation active={status === LOADING_STATUS}>
{status === LOADING_STATUS ? (
<FontAwesomeIcon icon={["fas", "spinner"]} />
) : status === READY_STATUS ? (
<FontAwesomeIcon icon={["fas", "paper-plane"]} />
) : status === SUCCESS_STATUS ? (
<FontAwesomeIcon icon={["fas", "check"]} />
) : (
<FontAwesomeIcon icon={["fas", "times"]} />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a restart icon would be more effective in telling the user to try resubmitting.

)}
</LoadingAnimation>
</EmailSubmitButton>
</EmailForm>
);
}