-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/captioning calculator #1
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
base: main
Are you sure you want to change the base?
Changes from all commits
868ed1a
0c2d1a6
83304bc
2ca051a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# This template uses this file instead of the typicial Netlify _redirects file. | ||
# For more information about redirects and rewrites, see https://docs.netlify.com/routing/redirects/. | ||
|
||
# Do not remove the line below. This is required to serve the site when deployed. | ||
/* /.netlify/functions/server 200 | ||
|
||
# Add other redirects and rewrites here and/or in your netlify.toml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
interface FormInputProps { | ||
type: string, | ||
name: string, | ||
id: string, | ||
image: string, | ||
label: string, | ||
value: string | number, | ||
handleInputChange: Function, | ||
required?: boolean, | ||
min?: string | ||
} | ||
|
||
const FormInput = ({ type, name, id, image, label, value, handleInputChange, required = false, min} : FormInputProps) => { | ||
return ( | ||
<div className="flex flex-col items-center text-center md:flex-row md:text-left"> | ||
{image && <img src={image} alt="" /> } | ||
<label htmlFor={id}> | ||
{label} | ||
<input | ||
className="rounded-3xl p-1" | ||
id={id} | ||
name={name} | ||
required={required} | ||
type={type} | ||
min={min} | ||
value={value} | ||
onChange={(e) => handleInputChange(e)} | ||
/> | ||
</label> | ||
</div> | ||
) | ||
} | ||
|
||
export default FormInput |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { useState } from 'react'; | ||
|
||
interface ToggleProps { | ||
onChange: Function | ||
} | ||
|
||
const Toggle = ({ onChange } : ToggleProps) => { | ||
const [isToggled, setToggle] = useState(false) | ||
|
||
const handleChange = (event : React.FormEvent<HTMLInputElement>) => { | ||
onChange(event); | ||
setToggle(!isToggled) | ||
} | ||
|
||
return ( | ||
<div className="toggle-btn mt-3 md:mt-0"> | ||
<input id="needsTranslations" name="needsTranslations" type="checkbox" defaultChecked={isToggled} onChange={handleChange} /> | ||
<span /> | ||
</div> | ||
) | ||
} | ||
|
||
export default Toggle |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,175 @@ | ||
import { useState } from "react"; | ||
import type { V2_MetaFunction } from "@remix-run/node"; | ||
import Toggle from "../components/toggle"; | ||
import FormInput from "../components/formInput" | ||
|
||
export const meta: V2_MetaFunction = () => { | ||
return [ | ||
{ title: "New Remix App" }, | ||
{ name: "description", content: "Welcome to Remix!" }, | ||
{ title: "Cablecast Captioning Calculator" }, | ||
{ | ||
name: "description", | ||
content: | ||
"Calculate how many captioning minutes you will need for a year of programming!", | ||
}, | ||
]; | ||
}; | ||
|
||
export default function Index() { | ||
const [formState, setFormState] = useState({ | ||
name: "", | ||
email: "", | ||
averageProgramsPerMonth: 0, | ||
averageLengthOfProgramsInHours: 0, | ||
needsTranslations: false, | ||
}); | ||
const [hasFormSubmit, setHasFormSubmit] = useState(false); | ||
const [captionMinutes, setCaptionMinutes] = useState(0); | ||
const [hasError, setHasError] = useState(false); | ||
|
||
const handleInputChange = (e: React.FormEvent<HTMLInputElement>) => { | ||
const { name, value } = e.target as HTMLInputElement; | ||
console.log(name, value); | ||
setFormState((prevProps) => ({ | ||
...prevProps, | ||
[name]: value, | ||
})); | ||
}; | ||
|
||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
if (!validateInput()) { | ||
setHasError(true); | ||
} else { | ||
try { | ||
fetch("/api/leads", { | ||
method: "POST", | ||
body: JSON.stringify(formState), | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
}); | ||
} catch (error) { | ||
console.log(error); | ||
} | ||
setHasFormSubmit(true); | ||
calcCaptionMinutes(); | ||
} | ||
}; | ||
|
||
const validateInput = () => { | ||
if (formState.name === "" || formState.name.length < 2) { | ||
return false; | ||
} else if (formState.email === "" || !formState.email.includes("@")) { | ||
return false; | ||
} else { | ||
return true; | ||
} | ||
}; | ||
|
||
const calcCaptionMinutes = () => { | ||
let captioningMinutes = | ||
formState.averageProgramsPerMonth * | ||
formState.averageLengthOfProgramsInHours * | ||
60; | ||
setCaptionMinutes(captioningMinutes); | ||
}; | ||
|
||
return ( | ||
<div> | ||
<h1 className="text-3xl font-bold underline">Hello world!</h1> | ||
{/* | ||
// Use the form below if you wan to go remix style, or just use fetch in an event handler. | ||
*/} | ||
<form action="/api/leads" method="POST"> | ||
<label htmlFor="name">Name</label> | ||
<input id="name" name="name" placeholder="Pat Smith" /> | ||
</form> | ||
<div className="app-container mx-auto index mt-12 px-5"> | ||
<h1 className="text-5xl uppercase text-center heading mb-5"> | ||
Captioning Calculator | ||
</h1> | ||
<div className="grid grid-cols-1 content-center md:grid-cols-2"> | ||
<div className="grid content-center text-center md:text-left"> | ||
<h2 className="text-3xl mb-3"> | ||
How many captioning minutes will you need? | ||
</h2> | ||
<p className="text-lg"> | ||
The Cablecast Captioning Calculator is a tool to understand how many | ||
Cablecast Captioning Minutes you will need for a year of | ||
programming. | ||
</p> | ||
</div> | ||
<div> | ||
<img src="cc-decorative-img.png" alt="" className="decorative-img" /> | ||
</div> | ||
</div> | ||
<div className="form-container rounded-3xl items-center mb-10"> | ||
{hasFormSubmit ? ( | ||
<> | ||
<h2 className="text-3xl text-center pt-10 mt-5"> | ||
You will need approximately | ||
</h2> | ||
<div className="flex items-center"> | ||
<p className="minutes-needed">{captionMinutes} minutes </p> | ||
</div> | ||
<h2 className="text-3xl text-center pb-10"> | ||
{" "} | ||
Of Closed Captioning for 1 year | ||
</h2> | ||
</> | ||
) : ( | ||
<form className="flex flex-col text-2xl mt-5" onSubmit={handleSubmit}> | ||
<FormInput | ||
type="text" | ||
label="Name" | ||
name="name" | ||
id="name" | ||
value={formState.name} | ||
handleInputChange={handleInputChange} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain why your handling each individual input change? What are the pros / cons? What would be another approach? |
||
image="name.png" | ||
required | ||
/> | ||
<FormInput | ||
type="email" | ||
label="Email" | ||
name="email" | ||
id="email" | ||
value={formState.email} | ||
handleInputChange={handleInputChange} | ||
image="email.png" | ||
required | ||
/> | ||
<FormInput | ||
type="number" | ||
label="Average Number of Programs Per Month" | ||
name="averageProgramsPerMonth" | ||
id="averageProgramsPerMonth" | ||
value={formState.averageProgramsPerMonth} | ||
handleInputChange={handleInputChange} | ||
image="number.png" | ||
min="0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice call on adding validation to these form inputs. Would probably put 1 here as if you have no programs a month the calculator isn't going to spit out a very useful answer :) |
||
required | ||
/> | ||
<FormInput | ||
type="number" | ||
label="Average Length of Program in Hours" | ||
name="averageLengthOfProgramsInHours" | ||
id="averageLengthOfProgramsInHours" | ||
value={formState.averageLengthOfProgramsInHours} | ||
handleInputChange={handleInputChange} | ||
image="clock.png" | ||
min="0" | ||
required | ||
/> | ||
<div className="flex flex-col items-center text-center md:flex-row md:text-left"> | ||
<img src="translate.png" alt="" /> | ||
<label htmlFor="needsTranslations" className="flex flex-col items-center md:flex-row"> | ||
Do You Need Translations? | ||
<Toggle onChange={handleInputChange} /> | ||
</label> | ||
</div> | ||
<div className="text-center"> | ||
{hasError && ( // if hasError is true, then render the error message | ||
<p className="text-red-500 mt-5">There was an error in your submission. Please ensure your name has at least 2 characters and your email contains an '@'.</p> | ||
)} | ||
<button className="uppercase btn green my-5 rounded-3xl px-7 py-2"> | ||
Calculate | ||
</button> | ||
</div> | ||
</form> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
body { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like your using a tailwind and some general styles? Can you explain why? |
||
color: #545c6f; | ||
font-family: Nunito Sans,sans-serif; | ||
font-size: .875rem; | ||
line-height: 1.5; | ||
font-weight: 600; | ||
} | ||
|
||
body:before { | ||
content: ''; | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
right: 0; | ||
height: 10px; | ||
background: #2cad66; | ||
} | ||
|
||
h1 { | ||
font-weight: 400; | ||
font-size: 6rem; | ||
} | ||
|
||
h2 { | ||
font-weight: 600; | ||
} | ||
|
||
.decorative-img { | ||
max-height: 300px; | ||
margin-left: auto; | ||
margin-right: auto; | ||
} | ||
|
||
.app-container { | ||
max-width: 1224px; | ||
} | ||
|
||
.form-container { | ||
background: #EDEEF1; | ||
} | ||
|
||
.form-container form { | ||
max-width: 900px; | ||
margin-left: auto; | ||
margin-right: auto; | ||
} | ||
|
||
.form-container form > div { | ||
margin-top: 20px; | ||
} | ||
|
||
.form-container div > img { | ||
width: 70px; | ||
} | ||
|
||
@media screen and (min-width: 767px) { | ||
.form-container div > img { | ||
margin-right: 10px; | ||
} | ||
} | ||
|
||
.form-container input, | ||
.minutes-needed { | ||
outline: none; | ||
border: solid #8e97aa 3px; | ||
padding-left: 10px; | ||
margin-left: 20px; | ||
margin-right: 20px; | ||
} | ||
|
||
.minutes-needed { | ||
text-align: center; | ||
margin: 3rem auto; | ||
border-radius: 25px; | ||
font-size: 3rem; | ||
border-radius: 50px; | ||
background: white; | ||
border-width: 5px; | ||
padding-left: 20px; | ||
padding-right: 20px; | ||
} | ||
|
||
.form-container input:focus { | ||
border: #2cad66 solid 3px; | ||
} | ||
|
||
.form-container .btn.green { | ||
background: #2cad66; | ||
color: white; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain the pros / cons of doing the validation client side like this?