Skip to content

Commit

Permalink
✨ update pricing page (#682)
Browse files Browse the repository at this point in the history
* update pricing page

* add husky

* add husky

* indent pricing

* indent submit

* remove husky

* signup button margin
  • Loading branch information
petar-cvit authored Nov 18, 2024
1 parent 751c43d commit c022f84
Show file tree
Hide file tree
Showing 5 changed files with 373 additions and 74 deletions.
38 changes: 14 additions & 24 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

281 changes: 281 additions & 0 deletions web/src/components/AdoptersPlanSignup/AdoptersPlanSignup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import { useState } from "react";
import styles from "./index.module.css";

const INIT = "INIT";
const SUBMITTING = "SUBMITTING";
const ERROR = "ERROR";
const SUCCESS = "SUCCESS";
const formStates = [INIT, SUBMITTING, ERROR, SUCCESS];
const formStyles = {
id: "clxnbxyqc0003ixgw637o04xb",
name: "Default",
placeholderText: "[email protected]",
formFont: "Inter",
formFontColor: "#fff",
formFontSizePx: 18,
buttonText: "Get your platform",
buttonFont: "Inter",
buttonFontColor: "#ffffff",
buttonFontSizePx: 22,
successMessage: "You rock! 🙌",
successFont: "Inter",
successFontColor: "#ffffff",
successFontSizePx: 20,
userGroup: "adopters plan",
};
const domain = "app.loops.so";

export default function AdoptersPlanSignup() {
const [email, setEmail] = useState("");
const [formState, setFormState] = useState(INIT);
const [errorMessage, setErrorMessage] = useState("");
const [fields, setFields] = useState({});

const resetForm = () => {
setEmail("");
setFormState(INIT);
setErrorMessage("");
};

/**
* Rate limit the number of submissions allowed
* @returns {boolean} true if the form has been successfully submitted in the past minute
*/
const hasRecentSubmission = () => {
const time = new Date();
const timestamp = time.valueOf();
const previousTimestamp = localStorage.getItem("loops-form-timestamp");

// Indicate if the last sign up was less than a minute ago
if (
previousTimestamp &&
Number(previousTimestamp) + 60 * 1000 > timestamp
) {
setFormState(ERROR);
setErrorMessage("Too many signups, please try again in a little while");
return true;
}

localStorage.setItem("loops-form-timestamp", timestamp.toString());
return false;
};

const handleSubmit = (event) => {
// Prevent the default form submission
event.preventDefault();

// boundary conditions for submission
if (formState !== INIT) return;
if (!isValidEmail(email)) {
setFormState(ERROR);
setErrorMessage("Please enter a valid email");
return;
}
if (hasRecentSubmission()) return;
setFormState(SUBMITTING);

// build additional fields
const additionalFields = Object.entries(fields).reduce(
(acc, [key, val]) => {
if (val) {
return acc + "&" + key + "=" + encodeURIComponent(val);
}
return acc;
},
"",
);

// build body
const formBody = `userGroup=${encodeURIComponent(
formStyles.userGroup,
)}&email=${encodeURIComponent(email)}&mailingLists=`;

// API request to add user to newsletter
fetch(`https://${domain}/api/newsletter-form/${formStyles.id}`, {
method: "POST",
body: formBody + additionalFields,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
})
.then((res) => [res.ok, res.json(), res])
.then(([ok, dataPromise, res]) => {
if (ok) {
resetForm();
setFormState(SUCCESS);
} else {
dataPromise.then((data) => {
setFormState(ERROR);
setErrorMessage(data.message || res.statusText);
localStorage.setItem("loops-form-timestamp", "");
});
}
})
.catch((error) => {
setFormState(ERROR);
// check for cloudflare error
if (error.message === "Failed to fetch") {
setErrorMessage(
"Too many signups, please try again in a little while",
);
} else if (error.message) {
setErrorMessage(error.message);
}
localStorage.setItem("loops-form-timestamp", "");
});
};

const isInline = formStyles.formStyle === "inline";

switch (formState) {
case SUCCESS:
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
}}
>
<p
style={{
fontFamily: `'${formStyles.successFont}', sans-serif`,
color: formStyles.successFontColor,
fontSize: `${formStyles.successFontSizePx}px`,
}}
>
{formStyles.successMessage}
</p>
</div>
);
case ERROR:
return (
<>
<SignUpFormError />
<BackButton />
</>
);
default:
return (
<>
<form
onSubmit={handleSubmit}
style={{
display: "flex",
flexDirection: isInline ? "row" : "column",
alignItems: "center",
justifyContent: "center",
width: "100%",
}}
>
<input
type="text"
name="email"
placeholder={formStyles.placeholderText}
value={email}
className={styles.adopterInput}
onChange={(e) => setEmail(e.target.value)}
required={true}
style={{
color: formStyles.formFontColor,
fontFamily: `'${formStyles.formFont}', sans-serif`,
fontSize: `${formStyles.formFontSizePx}px`,
margin: isInline ? "0px 10px 0px 0px" : "0px 0px 16px",
width: "80%",
minWidth: "100px",
background: "rgba(255, 255, 255, 0)",
border: "none",
borderBottom: "1px solid #D1D5DB",
boxSizing: "border-box",
padding: "8px 12px",
}}
/>
<div aria-hidden="true" style={{ position: "absolute" }}></div>
<SignUpFormButton />
</form>
</>
);
}

function SignUpFormError() {
return (
<div
style={{
alignItems: "center",
justifyContent: "center",
width: "100%",
}}
>
<p
style={{
fontFamily: "Inter, sans-serif",
color: "rgb(185, 28, 28)",
fontSize: "14px",
}}
>
{errorMessage || "Oops! Something went wrong, please try again"}
</p>
</div>
);
}

function BackButton() {
const [isHovered, setIsHovered] = useState(false);

return (
<button
style={{
color: "#6b7280",
font: "14px, Inter, sans-serif",
margin: "10px auto",
textAlign: "center",
background: "transparent",
border: "none",
cursor: "pointer",
textDecoration: isHovered ? "underline" : "none",
}}
onMouseOut={() => setIsHovered(false)}
onMouseOver={() => setIsHovered(true)}
onClick={resetForm}
>
&larr; Back
</button>
);
}

function SignUpFormButton() {
return (
<button
type="submit"
className={styles.submitButton}
style={{
fontSize: `${formStyles.buttonFontSizePx}px`,
color: formStyles.buttonFontColor,
fontFamily: `'${formStyles.buttonFont}', sans-serif`,
maxWidth: "300px",
whiteSpace: isInline ? "nowrap" : "normal",
height: "40px",
alignItems: "center",
justifyContent: "center",
flexDirection: "row",
padding: "9px 17px",
boxShadow: "0px 1px 2px rgba(0, 0, 0, 0.05)",
borderRadius: "40px",
textAlign: "center",
fontStyle: "normal",
fontWeight: 700,
lineHeight: "20px",
border: "none",
cursor: "pointer",
}}
>
{formState === SUBMITTING ? "Please wait..." : formStyles.buttonText}
</button>
);
}
}

function isValidEmail(email) {
return /.+@.+/.test(email);
}
13 changes: 13 additions & 0 deletions web/src/components/AdoptersPlanSignup/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.submitButton {
background-color: #ff8803;
}

.submitButton:hover {
background-color: #ffa229;
text-decoration: none;
}

.adopterInput::placeholder {
color: #fff;
opacity: 1; /* Firefox */
}
Loading

0 comments on commit c022f84

Please sign in to comment.