Refactor forms to use a generic form component

Replaced the contact form with a generic form component to support multiple form configurations. Moved validation schema and form data to a new structure, allowing dynamic form rendering. Updated the App component to utilize this generic approach for handling different forms.
This commit is contained in:
Teriuihi 2024-08-06 21:07:20 +02:00
parent 2eef14ba2e
commit d2f15d2627
7 changed files with 125 additions and 72 deletions

View File

@ -1,4 +1,3 @@
import ContactForm from "./components/contact_form/contact";
import './App.css';
import Home from "./components/home/home";
import {BrowserRouter, Route, Routes} from 'react-router-dom';
@ -7,6 +6,9 @@ import Footer from "./components/footer/footer";
import VerifyMail from "./components/verify_email/verify_mail";
import ThankYou from "./components/verify_email/thank_you";
import DEBUG from "./components/DEBUG/DEBUG";
import GenericForm from "./components/form/genericForm";
import {getFormProperties} from "./components/form/formData";
import {FormProperties} from "./components/form/formInterfaces";
function App() {
return (
@ -15,7 +17,13 @@ function App() {
<BrowserRouter basename="/">
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/contact" element={<ContactForm/>}/>
{getFormProperties().map((property: FormProperties) => (
<Route
key={property.path}
path={property.path}
element={<GenericForm {...property.formData} />}
/>
))}
<Route path="/verify-email" element={<VerifyMail/>}/>
<Route path="/thank-you" element={<ThankYou/>}/>
{process.env.NODE_ENV === 'development' && <Route path="/debug" element={<DEBUG/>}/>}

View File

@ -1,20 +0,0 @@
import * as Yup from 'yup';
export const validationSchema = Yup.object().shape({
username: Yup.string()
.min(3, 'Username should be at least 3 characters')
.max(16, 'Username should not exceed 16 characters')
.matches(/^[a-zA-Z0-9_]*$/, 'Username should only include alphanumeric characters and underscore')
.required('Username is required'),
email: Yup.string()
.email('Invalid email')
.min(3, 'Email should be at least 3 characters')
.max(254, 'Email should not exceed 254 characters')
.required(),
question: Yup.string()
.min(10, 'Question should be at least 10 characters')
.max(2000, 'Question should not exceed 2000 characters')
.required('Question is required')
});

View File

@ -0,0 +1,51 @@
import {FormData} from "../formInterfaces";
import * as Yup from "yup";
export const contact: FormData = {
steps: [
{
label: "Username",
name: "username",
type: "text",
min_length: 3,
max_length: 16,
required: true,
},
{
label: "Email",
name: "email",
type: "email",
min_length: 3,
max_length: 254,
required: true,
},
{
label: "Question",
name: "question",
type: "textarea",
min_length: 10,
max_length: 2000,
required: true,
},
],
backend: 'https://forms-backend.alttd.com/api/contact/submitContactForm',
userInput: {username: '', email: '', question: ''},
spec: Yup.object().shape({
username: Yup.string()
.min(3, 'Username should be at least 3 characters')
.max(16, 'Username should not exceed 16 characters')
.matches(/^[a-zA-Z0-9_]*$/, 'Username should only include alphanumeric characters and underscore')
.required('Username is required'),
email: Yup.string()
.email('Invalid email')
.min(3, 'Email should be at least 3 characters')
.max(254, 'Email should not exceed 254 characters')
.required(),
question: Yup.string()
.min(10, 'Question should be at least 10 characters')
.max(2000, 'Question should not exceed 2000 characters')
.required('Question is required')
})
};

View File

@ -0,0 +1,13 @@
import {FormProperties} from "./formInterfaces";
import {contact} from "./data/contact";
const formProperties: FormProperties[] = [
{
path: 'contact',
formData: contact
},
]
export function getFormProperties(): FormProperties[] {
return formProperties
}

View File

@ -0,0 +1,29 @@
import * as Yup from "yup";
type InputNames = "username" | "email" | "question" | "discord" | "age" | "pronoun" | "join-date" | "avg-time" |
"available-days" | "available-time" | "experience" | "why-staff" | "expectations-mod" | "other";
export interface Step {
label: string;
name: InputNames;
type: "text" | "email" | "textarea";
min_length: number;
max_length: number;
required: boolean;
}
export interface UserInput {
[key: string]: string;
}
export type FormData = {
steps: Step[];
backend: string;
userInput: UserInput;
spec: Yup.Schema<any>
}
export interface FormProperties {
path: string
formData: FormData;
}

View File

@ -1,61 +1,23 @@
import React, {useState} from "react";
import './Contact.css';
import './GenericForm.css';
import {useNavigate} from 'react-router-dom'
import {validationSchema} from "./validationSchema";
import {ErrorMessage, Field, Form, Formik, FormikValues} from "formik";
import {Step, UserInput, FormData} from './formInterfaces';
import * as Yup from "yup";
type InputNames = "username" | "email" | "question";
interface Step {
label: string;
name: InputNames;
type: "text" | "email" | "textarea";
min_length: number;
max_length: number;
required: boolean;
pattern: string;
}
const ContactForm = () => {
const GenericForm = (formData: FormData) => {
const steps: Step[] = formData.steps;
const backend: string = formData.backend;
const userInput: UserInput = formData.userInput;
const spec: Yup.Schema<any> = formData.spec;
const navigate = useNavigate();
const [currentStep, setCurrentStep] = useState<number>(0);
const steps: Step[] = [
{
label: "Username",
name: "username",
type: "text",
min_length: 3,
max_length: 16,
required: true,
pattern: ""
},
{
label: "Email",
name: "email",
type: "email",
min_length: 3,
max_length: 254,
required: true,
pattern: ""
},
{
label: "Question",
name: "question",
type: "textarea",
min_length: 10,
max_length: 2000,
required: true,
pattern: ""
},
]
const handleSubmit = async (e: FormikValues) => {
try {
const response = await fetch('https://forms-backend.alttd.com/api/contact/submitContactForm', {
const response = await fetch(backend, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -63,6 +25,16 @@ const ContactForm = () => {
body: JSON.stringify(e)
})
if (!response.ok) {
let json: string = JSON.stringify(steps);
const blob = new Blob([json], {type: "application/json"});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'form_data.json';
link.click();
URL.revokeObjectURL(url);
//TODO clean up
alert("Your form submission was denied by the server, or the server was unable to process it, if you didn't mess with the data please contact the administrator at admin@alttd.com");
} else {
navigate('/verify-email', {
@ -92,8 +64,8 @@ const ContactForm = () => {
</div>
<div>
<Formik
initialValues={{username: '', email: '', question: ''}}
validationSchema={validationSchema}
initialValues={userInput}
validationSchema={spec}
onSubmit={(values: FormikValues) => {
handleSubmit(values);
}}
@ -149,4 +121,4 @@ const ContactForm = () => {
);
};
export default ContactForm;
export default GenericForm;