-
Notifications
You must be signed in to change notification settings - Fork 769
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* update pricing page * add husky * add husky * indent pricing * indent submit * remove husky * signup button margin
- Loading branch information
1 parent
751c43d
commit c022f84
Showing
5 changed files
with
373 additions
and
74 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
281 changes: 281 additions & 0 deletions
281
web/src/components/AdoptersPlanSignup/AdoptersPlanSignup.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} | ||
> | ||
← 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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 */ | ||
} |
Oops, something went wrong.