Skip to content

Commit

Permalink
feat: add contact form (#390)
Browse files Browse the repository at this point in the history
* feat: add and setup form

* feat: add daisyui classes and logic

* feat: refactor form code

* feat: add contact links to footer and nav

* feat: add form error types

* feat: add animations

* feat: add successful submission notification

* feat: code cleanup

* feat: add form submission error notification

* feat: finishing up

* feat: modify notifcation logic

* feat:remove console.log

Co-authored-by: Joseph Maramba <[email protected]>
  • Loading branch information
mamba-dev-KE and Joseph Maramba authored Aug 23, 2022
1 parent 0a57e13 commit bd5360c
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"react-router-dom": "^6.2.1",
"react-scripts": "^5.0.1",
"react-supabase": "^0.2.0",
"react-toastify": "^9.0.8",
"swr": "^1.3.0",
"typescript": "^4.7.4",
"validator": "^13.7.0"
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import SLogin from "./components/accounts/SLogin";
import Profile from "./components/accounts/profile/Profile";
// import ResetPassword from "./components/accounts/settings/resetPassword";
import Settings from "./components/accounts/settings/Settings";
import Contact from "./components/contact/Contact";
import Home from "./components/home/Home";
import Organizations from "./components/organizations/Organizations";
import Favorites from "./components/pets/Favorites";
Expand All @@ -34,6 +35,7 @@ export default function App() {
<NavigationBar />
<Routes>
<Route path="animal/:id" element={<PetInfo />} />
<Route path="contact" element={<Contact />} />
<Route path="pets/:type" element={<PetType />} />
<Route path="pets" element={<Pets />} />
<Route path="about" element={<About />} />
Expand Down
78 changes: 78 additions & 0 deletions src/components/contact/Contact.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* ----------------------------------------------
* Generated by Animista on 2022-8-20 10:9:37
* Licensed under FreeBSD License.
* See http://animista.net/license for more info.
* w: http://animista.net, t: @cssanimista
* ---------------------------------------------- */

.slide-in-fwd-left {
-webkit-animation: slide-in-fwd-left 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94)
both;
animation: slide-in-fwd-left 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}

.tracking-in-expand-fwd {
-webkit-animation: tracking-in-expand-fwd 0.8s
cubic-bezier(0.215, 0.61, 0.355, 1) both;
animation: tracking-in-expand-fwd 0.8s cubic-bezier(0.215, 0.61, 0.355, 1)
both;
}

@-webkit-keyframes slide-in-fwd-left {
0% {
-webkit-transform: translateZ(-1400px) translateX(-1000px);
transform: translateZ(-1400px) translateX(-1000px);
opacity: 0;
}
100% {
-webkit-transform: translateZ(0) translateX(0);
transform: translateZ(0) translateX(0);
opacity: 1;
}
}
@keyframes slide-in-fwd-left {
0% {
-webkit-transform: translateZ(-1400px) translateX(-1000px);
transform: translateZ(-1400px) translateX(-1000px);
opacity: 0;
}
100% {
-webkit-transform: translateZ(0) translateX(0);
transform: translateZ(0) translateX(0);
opacity: 1;
}
}

@-webkit-keyframes tracking-in-expand-fwd {
0% {
letter-spacing: -0.5em;
-webkit-transform: translateZ(-700px);
transform: translateZ(-700px);
opacity: 0;
}
40% {
opacity: 0.6;
}
100% {
-webkit-transform: translateZ(0);
transform: translateZ(0);
opacity: 1;
}
}

@keyframes tracking-in-expand-fwd {
0% {
letter-spacing: -0.5em;
-webkit-transform: translateZ(-700px);
transform: translateZ(-700px);
opacity: 0;
}
40% {
opacity: 0.6;
}
100% {
-webkit-transform: translateZ(0);
transform: translateZ(0);
opacity: 1;
}
}
104 changes: 104 additions & 0 deletions src/components/contact/Contact.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { PawHubContainer } from "components/layout/Grid/PetCardFlex";
import { SubmitHandler, useForm } from "react-hook-form";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

import "./Contact.css";
import { EmailInput, MessageInput, NameInput } from "./Inputs";

export interface FormData {
names: string;
email: string;
message: string;
}

const Contact = () => {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<FormData>();

const onSubmit: SubmitHandler<FormData> = (data) => {
// TODO: tie the success/error notification to the respective submission states
if (data) {
reset();
successNotification();
return;
}

errorNotification();
};

/* form submission notification */
const inputClasses = errors.names
? "input input-error text-lg w-full max-w-3xl bg-white my-2"
: "input input-bordered text-lg w-full max-w-3xl bg-white my-2";

const successNotification = () =>
toast.success("Sent successfully! 🐿🐿🐿", {
position: "top-center",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: false,
draggable: true,
progress: undefined,
});

const errorNotification = () =>
toast.error("An error occurred! ⛔⛔⛔", {
position: "top-center",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: false,
draggable: true,
progress: undefined,
});

return (
<PawHubContainer>
<section>
<h1 className="tracking-in-expand-fwd font-amatic text-5xl font-bold py-10">
Contact Us
</h1>
<form
onSubmit={handleSubmit(onSubmit)}
className="slide-in-fwd-left form-control max-w-lg"
>
<label className="text-lg">
Names
<NameInput
register={register}
inputClasses={inputClasses}
errors={errors}
/>
</label>

<label className="text-lg">
Email
<EmailInput
register={register}
inputClasses={inputClasses}
errors={errors}
/>
</label>

<label htmlFor="message" className="text-lg py-2">
Message
</label>
<MessageInput register={register} errors={errors} />

<button className="btn btn-primary rounded-full w-28 mt-4">
Submit
</button>
</form>
</section>
<ToastContainer />
</PawHubContainer>
);
};

export default Contact;
72 changes: 72 additions & 0 deletions src/components/contact/Inputs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { FieldErrorsImpl, UseFormRegister } from "react-hook-form";

import { FormData } from "./Contact";

interface InputProps {
inputClasses?: string;
register: UseFormRegister<FormData>;
errors: FieldErrorsImpl<FormData>;
}

export const NameInput = ({ inputClasses, register, errors }: InputProps) => {
return (
<>
<input
placeholder="Names"
{...register("names", {
required: "This field is required.",
maxLength: 50,
minLength: 6,
})}
className={inputClasses}
/>
<div className="py-4 text-red-500 text-sm">
{errors && <span>{errors.names?.message}</span>}
{errors.names?.type === "minLength" && (
<span> Enter atleast 6 characters</span>
)}
</div>
</>
);
};

export const EmailInput = ({ inputClasses, register, errors }: InputProps) => {
return (
<>
<input
placeholder="Email"
{...register("email", {
required: "This field is required.",
pattern: /^\S+@\S+$/i,
})}
className={inputClasses}
/>
<div className="py-4 text-red-500 text-sm">
{errors && <span>{errors.email?.message}</span>}
{errors.email?.type === "pattern" && (
<span> Enter a valid email address</span>
)}
</div>
</>
);
};

export const MessageInput = ({ register, errors }: InputProps) => {
return (
<>
<textarea
{...register("message", { required: "This field is required." })}
className={
errors.message
? "textarea textarea-error h-40 text-lg bg-white rounded-lg"
: "textarea textarea-bordered h-40 text-lg bg-white rounded-lg"
}
placeholder="Message"
id="message"
/>
<div className="py-4 text-red-500 text-sm">
{errors && <span>{errors.message?.message}</span>}
</div>
</>
);
};
3 changes: 3 additions & 0 deletions src/components/layout/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const FooterMenu = () => {
<Link to="/tips" className="link link-hover">
Tips
</Link>
<Link to="/contact" className="link link-hover">
Contact
</Link>
</article>
</Footer>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/layout/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default function NavigationBar() {
<MenuItemNavLink name="About" route="about" />
<MenuItemNavLink name="Resources" route="resources" />
<MenuItemNavLink name="Organizations" route="organizations" />
<MenuItemNavLink name="Contact" route="contact" />
</Menu>
</Navbar.Center>
<Navbar.End>
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3537,6 +3537,11 @@ clone@^1.0.2:
resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=

clsx@^1.1.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==

co@^4.6.0:
version "4.6.0"
resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz"
Expand Down Expand Up @@ -8617,6 +8622,13 @@ react-test-renderer@^18.2.0:
react-shallow-renderer "^16.15.0"
scheduler "^0.23.0"

react-toastify@^9.0.8:
version "9.0.8"
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-9.0.8.tgz#3876c89fc6211a29027b3075010b5ec39ebe4f7e"
integrity sha512-EwM+teWt49HSHx+67qI08yLAW1zAsBxCXLCsUfxHYv1W7/R3ZLhrqKalh7j+kjgPna1h5LQMSMwns4tB4ww2yQ==
dependencies:
clsx "^1.1.1"

react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
Expand Down

0 comments on commit bd5360c

Please sign in to comment.