Skip to content

Commit

Permalink
Merge branch 'develop' into ft-trainee-application
Browse files Browse the repository at this point in the history
  • Loading branch information
robsdagreat committed Oct 4, 2024
2 parents fcb1866 + bd88713 commit ae7e1d8
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 290 deletions.
2 changes: 1 addition & 1 deletion src/components/form/SignInForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const LoginForm = () => {
const role = localStorage.getItem("roleName") as string;
if (role === "applicant") {
navigate("/applicant");
} else if (role === "superAdmin") {
} else if (role === "superAdmin" || role === "admin") {
navigate("/admin");
} else {
const searchParams = new URLSearchParams(location.search);
Expand Down
115 changes: 55 additions & 60 deletions src/components/form/TraineeApply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { getAllCycles } from '../../redux/actions/cyclesActions';
import Button from './Button';
import InputField from './InputField';
import { useNavigate } from 'react-router';
import { AppDispatch, RootState } from '../../redux/store'; // Adjust the import path as needed
import { AppDispatch, RootState } from '../../redux/store';
import useFormValidation from '../useFormValidation'

interface FormData {
firstName: string;
Expand All @@ -26,22 +27,51 @@ interface Cycle {
name: string;
}

const initialFormData: FormData = {
firstName: '',
lastName: '',
email: '',
cycle_id: ''
};

const CycleSelector: React.FC<{
value: string,
onChange: (e: ChangeEvent<HTMLSelectElement>) => void,
cycles: Cycle[],
cyclesLoading: boolean,
error?: string
}> = ({ value, onChange, cycles, cyclesLoading, error }) => (
<>
<select
name="cycle_id"
value={value}
onChange={onChange}
className="w-52 md:w-2/3 rounded-md px-2 py-2 border border-white placeholder:text-gray-400 text-white sm:text-[12px] outline-none autofill:bg-transparent autofill:text-white bg-[#1F2A37]"
disabled={cyclesLoading}
>
<option value="">Select Application Cycle</option>
{cycles.map((cycle: Cycle) => (
<option key={cycle.id} value={cycle.id}>
{cycle.name}
</option>
))}
</select>
{error && <p className="text-red-500">{error}</p>}
</>
);

const TraineeApplicationForm: React.FC = () => {
const dispatch = useDispatch<AppDispatch>();
const navigate = useNavigate();
const { loading, error } = useSelector((state: RootState) => state.trainee);
const [formData, setFormData] = useState<FormData>({
firstName: '',
lastName: '',
email: '',
cycle_id: ''
});
const [errors, setErrors] = useState<FormErrors>({});
const [formData, setFormData] = useState<FormData>(initialFormData);
const [submitError, setSubmitError] = useState<string | null>(null);

const cycles = useSelector((state: RootState) => state.cycles.data);
const cyclesLoading = useSelector((state: RootState) => state.cycles.isLoading);

const { errors, validateForm } = useFormValidation(formData);

useEffect(() => {
dispatch(getAllCycles());
}, [dispatch]);
Expand All @@ -54,18 +84,6 @@ const TraineeApplicationForm: React.FC = () => {
}));
};

const validateForm = (): boolean => {
let newErrors: FormErrors = {};
if (!formData.firstName.trim()) newErrors.firstName = 'First name is required';
if (!formData.lastName.trim()) newErrors.lastName = 'Last name is required';
if (!formData.email.trim()) newErrors.email = 'Email is required';
else if (!/\S+@\S+\.\S+/.test(formData.email)) newErrors.email = 'Email is invalid';
if (!formData.cycle_id.trim()) newErrors.cycle_id = 'Cycle ID is required';

setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};

const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (validateForm()) {
Expand All @@ -87,48 +105,25 @@ const TraineeApplicationForm: React.FC = () => {
<div className="p-20 border border-primary shadow-xl bg-slate-800 rounded mt-20">
<h2 className="text-2xl font-semibold mb-6 text-white text-center">Trainee Application</h2>
<form onSubmit={handleSubmit} className="space-y-4 pt-5">
<InputField
name="firstName"
type="text"
placeholder="First Name"
value={formData.firstName}
onChange={handleInputChange}
error={errors.firstName}
className="w-52 md:w-2/3 rounded-md px-2 py-2 border border-white placeholder:text-gray-400 text-white sm:text-[12px] outline-none autofill:bg-transparent autofill:text-white bg-[#1F2A37]"
/>
<InputField
name="lastName"
type="text"
placeholder="Last Name"
value={formData.lastName}
onChange={handleInputChange}
error={errors.lastName}
className="w-52 md:w-2/3 rounded-md px-2 py-2 border border-white placeholder:text-gray-400 text-white sm:text-[12px] outline-none autofill:bg-transparent autofill:text-white bg-[#1F2A37]"
/>
<InputField
name="email"
type="email"
placeholder="Email"
value={formData.email}
onChange={handleInputChange}
error={errors.email}
className="w-52 md:w-2/3 rounded-md px-2 py-2 border border-white placeholder:text-gray-400 text-white sm:text-[12px] outline-none autofill:bg-transparent autofill:text-white bg-[#1F2A37]"
/>
<select
name="cycle_id"
{['firstName', 'lastName', 'email'].map((field) => (
<InputField
key={field}
name={field}
type={field === 'email' ? 'email' : 'text'}
placeholder={field.charAt(0).toUpperCase() + field.slice(1).replace(/([A-Z])/g, ' $1')}
value={formData[field as keyof FormData]}
onChange={handleInputChange}
error={errors[field as keyof FormErrors]}
className="w-52 md:w-2/3 rounded-md px-2 py-2 border border-white placeholder:text-gray-400 text-white sm:text-[12px] outline-none autofill:bg-transparent autofill:text-white bg-[#1F2A37]"
/>
))}
<CycleSelector
value={formData.cycle_id}
onChange={handleInputChange}
className="w-52 md:w-2/3 rounded-md px-2 py-2 border border-white placeholder:text-gray-400 text-white sm:text-[12px] outline-none autofill:bg-transparent autofill:text-white bg-[#1F2A37]"
disabled={cyclesLoading}
>
<option value="">Select Application Cycle</option>
{cycles.map((cycle: Cycle) => (
<option key={cycle.id} value={cycle.id}>
{cycle.name}
</option>
))}
</select>
{errors.cycle_id && <p className="text-red-500">{errors.cycle_id}</p>}
cycles={cycles}
cyclesLoading={cyclesLoading}
error={errors.cycle_id}
/>
<Button
type="submit"
label="Submit Application"
Expand Down
2 changes: 1 addition & 1 deletion src/components/profileDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function ProfileDropdown({
<div
className="w-full p-3 flex flex-row align-center justify-start text-gray-900 dark:text-black -100 dark:hover:bg-gray-300 dark:hover:text-gray-900 hover:bg-gray-600 hover:rounded-b-[20px] hover:text-gray-100 "
onClick={() => {
localStorage.removeItem("access_token");
localStorage.clear();
}}
>
<Link
Expand Down
48 changes: 48 additions & 0 deletions src/components/useFormValidation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState } from 'react';

interface FormData {
firstName: string;
lastName: string;
email: string;
cycle_id: string;
}

interface FormErrors {
firstName?: string;
lastName?: string;
email?: string;
cycle_id?: string;
}

const validateField = (field: string, value: string): string | undefined => {
switch (field) {
case 'firstName':
case 'lastName':
return value.trim() ? undefined : `${field.charAt(0).toUpperCase() + field.slice(1)} is required`;
case 'email':
if (!value.trim()) return 'Email is required';
return /\S+@\S+\.\S+/.test(value) ? undefined : 'Email is invalid';
case 'cycle_id':
return value.trim() ? undefined : 'Cycle ID is required';
default:
return undefined;
}
};

const useFormValidation = (formData: FormData) => {
const [errors, setErrors] = useState<FormErrors>({});

const validateForm = (): boolean => {
const newErrors: FormErrors = Object.keys(formData).reduce((acc, field) => {
const error = validateField(field, formData[field as keyof FormData]);
return error ? { ...acc, [field]: error } : acc;
}, {});

setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};

return { errors, validateForm };
};

export default useFormValidation;
9 changes: 5 additions & 4 deletions src/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ const LoginPage = (props: any) => {
}
`;

return authenticated && roleName === "applicant" ?
<Navigate to="/applicant" /> : authenticated && roleName === "superAdmin" ? <Navigate to="/" />
:
(
return authenticated && roleName === "applicant" ? (
<Navigate to="/applicant" />
) : authenticated && (roleName === "superAdmin" || roleName === "admin") ? (
<Navigate to="/admin" />
) : (
<>
<div className="flex items-center dark:bg-zinc-800 ">
<div
Expand Down
2 changes: 1 addition & 1 deletion src/pages/LogoutPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Icon } from "@iconify/react";
const LogoutPage = () => {
const navigate = useNavigate();
const handleLogout = async (e: any) => {
localStorage.removeItem("access_token");
localStorage.clear();
navigate("/login");
};
return (
Expand Down
2 changes: 1 addition & 1 deletion src/pages/PageNotFound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const PageNotFound = () => {
<Link to="/applicant">
<button>Go to Applicant Dashboard</button>
</Link>
) : role === "superAdmin" ? (
) : (role === "superAdmin" || role === "admin") ? (
<Link to="/admin">
<button>Go back to Homepage</button>
</Link>
Expand Down
Loading

0 comments on commit ae7e1d8

Please sign in to comment.