Skip to content

Commit

Permalink
React hook form
Browse files Browse the repository at this point in the history
  • Loading branch information
w3bdesign committed Aug 5, 2024
1 parent fc2015a commit e228f32
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 59 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"dependencies": {
"@emailjs/browser": "^4.4.1",
"@hookform/resolvers": "^3.9.0",
"@portabletext/react": "^3.1.0",
"@sanity/client": "^6.21.1",
"@sanity/image-url": "^1.0.2",
Expand All @@ -28,10 +29,12 @@
"next-sanity": "^9.4.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.2",
"react-icons": "^5.2.1",
"react-use": "^17.5.1",
"sanity": "^3.52.4",
"sitemap": "^8.0.0"
"sitemap": "^8.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@ladle/react": "^4.1.0",
Expand Down
27 changes: 27 additions & 0 deletions pnpm-lock.yaml

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

87 changes: 41 additions & 46 deletions src/components/Kontakt/KontaktContent.component.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,52 @@
"use client";

import { useState, useRef } from "react";
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import emailjs from "@emailjs/browser";

import Button from "@/components/UI/Button.component";
import PageHeader from "@/components/UI/PageHeader.component";
import InputField from "@/components/UI/InputField.component";

interface IEvent {
preventDefault: () => void;
}
const formSchema = z.object({
navn: z
.string()
.min(1, "Navn er påkrevd")
.regex(/^[a-zA-ZæøåÆØÅ ]+$/, "Vennligst bruk norske bokstaver"),
telefon: z
.string()
.min(8, "Telefonnummer må være minst 8 siffer")
.regex(/^[0-9]+$/, "Vennligst bruk bare tall"),
tekst: z.string().min(1, "Melding er påkrevd"),
});

/**
* Renders contact form. Uses EmailJS to send the emails.
* @function KontaktContent
* @returns {JSX.Element} - Rendered component
*/
type FormData = z.infer<typeof formSchema>;

const KontaktContent = () => {
const formRef = useRef<HTMLFormElement>(null);

const [serverResponse, setServerResponse] = useState<string>("");
const [submitting, setSubmitting] = useState<boolean>(false);
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>({
resolver: zodResolver(formSchema),
});

/**
* Handles the form submission and sends an email using the provided API keys.
*
* @param {IEvent} event - The event object representing the form submission event.
* @return {void} No return value.
*/
const handleSubmit = (event: IEvent) => {
const onSubmit = async (data: FormData) => {
const EMAIL_API_KEY = process.env.NEXT_PUBLIC_EMAIL_API_KEY ?? "changeme";
const TEMPLATE_KEY =
process.env.NEXT_PUBLIC_EMAIL_TEMPLATE_KEY ?? "changeme";
const SERVICE_KEY = process.env.NEXT_PUBLIC_EMAIL_SERVICE_KEY ?? "changeme";

// Disable button
setSubmitting(true);

event.preventDefault();

if (!formRef.current) {
return;
try {
emailjs.init(EMAIL_API_KEY);
await emailjs.send(SERVICE_KEY, TEMPLATE_KEY, data);
setServerResponse("Takk for din beskjed");
} catch (error) {
setServerResponse("Feil under sending av skjema");
}

emailjs.init(EMAIL_API_KEY);
emailjs.sendForm(SERVICE_KEY, TEMPLATE_KEY, formRef.current).then(
() => {
setServerResponse("Takk for din beskjed");
},
() => {
setServerResponse("Feil under sending av skjema");
},
);
};

return (
Expand All @@ -71,8 +65,7 @@ const KontaktContent = () => {
<form
id="contact-form"
className="text-center"
ref={formRef}
onSubmit={handleSubmit}
onSubmit={handleSubmit(onSubmit)}
method="POST"
action="/api/form"
aria-label="Contact Form"
Expand All @@ -85,31 +78,33 @@ const KontaktContent = () => {
inputName="navn"
label="Fullt navn"
htmlFor="navn"
inputPattern="[a-zA-ZæøåÆØÅ ]+"
title="Vennligst bruk norske bokstaver"
isRequired
register={register}
error={errors.navn}
isRequired={true}
/>
<br />
<InputField
inputName="telefon"
label="Telefonnummer"
htmlFor="telefon"
isRequired
inputPattern=".[0-9]{7}"
title="Vennligst bruk bare tall"
register={register}
error={errors.telefon}
isRequired={true}
/>
<br />
<InputField
inputName="tekst"
type="textarea"
label="Hva ønsker du å si?"
htmlFor="tekst"
isRequired
register={register}
error={errors.tekst}
isRequired={true}
/>
<br />
</fieldset>
<div className="-mt-4">
<Button disabled={submitting}>Send skjema</Button>
<Button disabled={isSubmitting}>Send skjema</Button>
</div>
</form>
)}
Expand Down
31 changes: 19 additions & 12 deletions src/components/UI/InputField.component.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import { UseFormRegister, FieldError } from "react-hook-form";

interface IInputProps {
inputName: string;
Expand All @@ -8,43 +9,46 @@ interface IInputProps {
inputPattern?: string;
title?: string;
type?: "input" | "textarea";
register: UseFormRegister<any>;
error?: FieldError | undefined;
}

const InputField = ({
const InputField: React.FC<IInputProps> = ({
inputName,
label,
inputPattern,
isRequired,
htmlFor,
title,
type = "input",
register,
error,
...props
}: IInputProps) => {
}) => {
const sharedClasses =
"cursor-pointer peer block text-xl w-64 p-2 bg-gray-800 text-slate-200 border-gray-500 border rounded border-opacity-50 outline-none focus:border-slate-200 placeholder-gray-300 placeholder-opacity-0 transition duration-200";

return (
<div className="relative my-2 flex justify-center">
<div className="relative my-2 flex flex-col items-center">
<div className="relative">
{type === "input" ? (
<input
name={inputName}
id={htmlFor}
type="text"
placeholder={label}
required={isRequired}
pattern={inputPattern}
title={title}
className={sharedClasses}
className={`${sharedClasses} ${error ? 'border-red-500' : ''}`}
{...register(inputName, {
required: isRequired,
pattern: inputPattern ? new RegExp(inputPattern) : undefined,
})}
{...props}
/>
) : (
<textarea
name={inputName}
id={htmlFor}
placeholder={label}
className={sharedClasses}
required={isRequired}
className={`${sharedClasses} ${error ? 'border-red-500' : ''}`}
{...register(inputName, { required: isRequired })}
{...props}
></textarea>
)}
Expand All @@ -57,8 +61,11 @@ const InputField = ({
{label}
</label>
</div>
{error && (
<p className="text-xs italic text-red-500 mt-2">{error.message}</p>
)}
</div>
);
};

export default InputField;
export default InputField;

0 comments on commit e228f32

Please sign in to comment.