Skip to content

Commit

Permalink
Merge pull request #153 from snaeem3/151-feedback-form
Browse files Browse the repository at this point in the history
Feedback Form changes and bug fixes
  • Loading branch information
snaeem3 authored Jul 19, 2024
2 parents 18c9453 + 1b17137 commit af5f79e
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 135 deletions.
44 changes: 44 additions & 0 deletions frontend/src/apiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import axios from "axios";
import { FormValues } from "./pages/Feedback/FeedbackForm";
const baseURL = import.meta.env.VITE_API_BASE_URL;

const api = axios.create({
baseURL,
headers: {
Authorization: `JWT ${localStorage.getItem("access")}`,
},
});

// Request interceptor to set the Authorization header
api.interceptors.request.use(
(configuration) => {
const token = localStorage.getItem("access");
if (token) {
configuration.headers.Authorization = `JWT ${token}`;
}
return configuration;
},
(error) => Promise.reject(error)
);

const handleSubmitFeedback = async (
feedbackType: FormValues["feedbackType"],
name: FormValues["name"],
email: FormValues["email"],
message: FormValues["message"]
) => {
try {
const response = await api.post(`/jira/feedback/`, {
feedbacktype: feedbackType,
name,
email,
message,
});
return response.data;
} catch (error) {
console.error("Error(s) during handleSubmitFeeedback: ", error);
throw error;
}
};

export { handleSubmitFeedback };
208 changes: 131 additions & 77 deletions frontend/src/pages/Feedback/FeedbackForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,39 @@ import { useEffect, useState } from "react";
import { useFormik } from "formik";
import { useMutation } from "react-query";
import { object, string } from "yup";
import axios from "axios";
import axios, { AxiosError } from "axios";
import { handleSubmitFeedback } from "../../apiClient";

interface FormValues {
export interface FormValues {
feedbackType: "new_feature" | "issue" | "general" | "";
name: string;
email: string;
message: string;
image: string;
}

// Error Message interface and validation
interface ErrorMessages {
[key: string]: string[];
}
const isValidErrorMessages = (data: unknown): data is ErrorMessages => {
if (typeof data !== "object" || data === null) return false;
return Object.entries(data).every(
([key, value]) =>
typeof key === "string" &&
Array.isArray(value) &&
value.every((item) => typeof item === "string")
);
};

const FeedbackForm = () => {
const [feedback, setFeedback] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [errorMessages, setErrorMessages] = useState<ErrorMessages>({});
const [isPressed, setIsPressed] = useState(false);
const [selectedImage, setSelectedImage] = useState<File | null>(null);

const feedbackValidation = object().shape({
feedbackType: string().required("You must select a feedback type"),
name: string().required("Name is a required field"),
email: string()
.email("You have entered an invalid email")
Expand All @@ -33,18 +50,19 @@ const FeedbackForm = () => {
feedbackMessage.innerText = feedback;
}

// TO-DO: Code below may not be necessary
// Update an error message div after Submit
const errorMessageDiv = document.getElementById("error-message");
if (errorMessageDiv) {
errorMessageDiv.innerText = errorMessage;
}
}, [feedback, errorMessage]);
// const errorMessageDiv = document.getElementById("error-message");
// if (errorMessageDiv) {
// errorMessageDiv.innerText = errorMessage;
// }
}, [feedback, errorMessages]);

//reset the form fields and states when clicking cancel
const handleCancel = () => {
resetForm();
setFeedback("");
setErrorMessage("");
setErrorMessages({});
};

const handleMouseDown = () => {
Expand Down Expand Up @@ -79,85 +97,99 @@ const FeedbackForm = () => {
const { errors, handleChange, handleSubmit, resetForm, touched, values } =
useFormik<FormValues>({
initialValues: {
feedbackType: "",
name: "",
email: "",
message: "",
image: "",
},
validationSchema: feedbackValidation,
onSubmit: async (values) => {
setFeedback("");
try {
// Call 1: Create Feedback request
const response = await axios.post(
"http://localhost:8000/api/jira/create_new_feedback/",
{
name: values.name,
email: values.email,
message: values.message,
},
{
headers: {
"Content-Type": "application/json",
},
}
await handleSubmitFeedback(
values.feedbackType,
values.name,
values.email,
values.message
);

setFeedback("Feedback submitted successfully!");
resetForm();
setErrorMessages({});

// TO-DO: Commented code below needs to be updated for image submission
// check to see if request was successful and get the issue key
if (response.data.status === 201) {
const issueKey = response.data.issueKey;
// if (response.data.status === 201) {
// const issueKey = response.data.issueKey;

if (values.image) {
// Call 2: Upload Image
const formData = new FormData();
formData.append("issueKey", issueKey);
formData.append("attachment", values.image);
// if (values.image) {
// // Call 2: Upload Image
// const formData = new FormData();
// formData.append("issueKey", issueKey);
// formData.append("attachment", values.image);

const response2 = await axios.post(
"http://localhost:8000/api/jira/upload_servicedesk_attachment/",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
// const response2 = await axios.post(
// "http://localhost:8000/api/jira/upload_servicedesk_attachment/",
// formData,
// {
// headers: {
// "Content-Type": "multipart/form-data",
// },
// }
// );

// Check if attachment upload was successful
if (response2.data.status === 200) {
const attachmentId = response2.data.tempAttachmentId;
// // Check if attachment upload was successful
// if (response2.data.status === 200) {
// const attachmentId = response2.data.tempAttachmentId;

// Step 3: Attach upload image to feedback request
const response3 = await axios.post(
"http://localhost:8000/api/jira/attach_feedback_attachment/",
{
issueKey: issueKey,
tempAttachmentId: attachmentId,
}
);
// // Step 3: Attach upload image to feedback request
// const response3 = await axios.post(
// "http://localhost:8000/api/jira/attach_feedback_attachment/",
// {
// issueKey: issueKey,
// tempAttachmentId: attachmentId,
// }
// );

// Check if the attachment was successfully attached
if (response3.status === 200) {
setFeedback("Feedback and image submitted successfully!");
resetForm();
} else {
setErrorMessage("Error attaching image");
}
} else {
setErrorMessage("Error uploading the image.");
console.log(response2);
}
// // Check if the attachment was successfully attached
// if (response3.status === 200) {
// setFeedback("Feedback and image submitted successfully!");
// resetForm();
// } else {
// setErrorMessage("Error attaching image");
// }
// } else {
// setErrorMessage("Error uploading the image.");
// console.log(response2);
// }
// } else {
// setFeedback("Feedback submitted successfully!");
// resetForm();
// }
// } else {
// setErrorMessage(`Error(s): ${response.data.message}`);
// }
} catch (error) {
console.error(error);
if (error instanceof AxiosError && error.response) {
const data = error.response.data;
if (isValidErrorMessages(data)) {
// Handle expected error such as missing/invalid form field
setErrorMessages(data);
} else {
setFeedback("Feedback submitted successfully!");
resetForm();
// Handle unexpected error such as invalid JWT token
setErrorMessages({ "Axios Error": [error.message] });
}
} else if (error instanceof Error) {
setErrorMessages({ Error: [error.message] });
} else {
setErrorMessage("Error creating a new feedback request.");
// Handle unknown error
setErrorMessages({ Error: ["Unknown Error"] });
}
} catch (error) {
setErrorMessage("An error occurred while submitting the form");
}
},
validationSchema: feedbackValidation,
});

return (
Expand All @@ -178,44 +210,57 @@ const FeedbackForm = () => {
<div className="flex items-center gap-x-3 pr-16">
<input
id="feature-request"
name="feedback-type"
name="feedbackType"
type="radio"
className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600"
value="Yes"
value="new_feature"
onChange={handleChange}
checked={values.feedbackType === "new_feature"}
/>
<label
className="block text-sm font-medium leading-6 text-gray-900"
htmlFor="psychotic-yes"
htmlFor="feature-request"
>
Feature request
</label>
<input
id="bug"
name="feedback-type"
id="issue"
name="feedbackType"
type="radio"
className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600"
value="No"
value="issue"
onChange={handleChange}
checked={values.feedbackType === "issue"}
/>
<label
className="block text-sm font-medium leading-6 text-gray-900"
htmlFor="psychotic-no"
htmlFor="issue"
>
Issue
</label>
<input
id="general-improvements"
name="feedback-type"
id="general"
name="feedbackType"
type="radio"
className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600"
value="No"
value="general"
onChange={handleChange}
checked={values.feedbackType === "general"}
/>
<label
className="block text-sm font-medium leading-6 text-gray-900"
htmlFor="psychotic-no"
htmlFor="general"
>
General feedback
</label>
</div>
<div className="form-error-container">
{touched.feedbackType && errors.feedbackType && (
<p className="text-sm text-red-500">
{errors.feedbackType}
</p>
)}
</div>
</dd>
</fieldset>
<div className="mb-4">
Expand Down Expand Up @@ -404,7 +449,16 @@ const FeedbackForm = () => {
</button>
</div>
<div id="feedback-message">{feedback}</div>
<div id="error-message">{errorMessage}</div>
{Object.entries(errorMessages).map(([field, messages], index) => (
<div key={index} className="error-message">
<strong>{field}:</strong>
<ul>
{messages.map((message, idx) => (
<li key={idx}>{message}</li>
))}
</ul>
</div>
))}
</div>
</form>
</section>
Expand Down
Loading

0 comments on commit af5f79e

Please sign in to comment.