Skip to content

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

Open
wants to merge 4 commits into
base: main
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
7 changes: 7 additions & 0 deletions _app_redirects
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
34 changes: 34 additions & 0 deletions app/components/formInput.tsx
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
23 changes: 23 additions & 0 deletions app/components/toggle.tsx
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
9 changes: 7 additions & 2 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import {
ScrollRestoration,
} from "@remix-run/react";

import stylesheet from "~/tailwind.css";

import stylesheet from "./styles/tailwind.css";
import styles from "./styles/app.css"
import toggleStyles from "./styles/toggle.css"

export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
{ rel: "preconnect", href: "https://fonts.googleapis.com", crossOrigin: "anonymous"},
{ rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;900&display=swap", crossOrigin: "anonymous"},
{ rel: "stylesheet", href: styles},
{ rel: "stylesheet", href: toggleStyles}
];

export default function App() {
Expand Down
174 changes: 163 additions & 11 deletions app/routes/_index.tsx
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 = () => {
Copy link
Member

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?

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}
Copy link
Member

Choose a reason for hiding this comment

The 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"
Copy link
Member

Choose a reason for hiding this comment

The 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>
);
}
90 changes: 90 additions & 0 deletions app/styles/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
body {
Copy link
Member

Choose a reason for hiding this comment

The 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;
}
File renamed without changes.
Loading