Skip to content

Commit

Permalink
Add success & error UIs for VSR form
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminJohnson2204 committed Mar 31, 2024
1 parent ba52b37 commit cc61737
Show file tree
Hide file tree
Showing 8 changed files with 411 additions and 36 deletions.
4 changes: 4 additions & 0 deletions frontend/public/ic_close_large.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
200 changes: 165 additions & 35 deletions frontend/src/app/vsr/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,19 @@ import PageNumber from "@/components/VSRForm/PageNumber";
import { createVSR, CreateVSRRequest, FurnitureInput } from "@/api/VSRs";
import { FurnitureItem, getFurnitureItems } from "@/api/FurnitureItems";
import BinaryChoice from "@/components/shared/input/BinaryChoice";
import { FurnitureItemSelection } from "@/components/VeteranForm/FurnitureItemSelection";
import { ConfirmVSRSubmissionModal } from "@/components/VSRForm/ConfirmVSRSubmissionModal";
import { FurnitureItemSelection } from "@/components/VSRForm/FurnitureItemSelection";
import { useScreenSizes } from "@/hooks/useScreenSizes";
import { VSRErrorModal } from "@/components/VSRForm/VSRErrorModal";
import Image from "next/image";

enum VSRFormError {
CANNOT_RETRIEVE_FURNITURE_NO_INTERNET,
CANNOT_RETRIEVE_FURNITURE_INTERNAL,
CANNOT_SUBMIT_NO_INTERNET,
CANNOT_SUBMIT_INTERNAL,
NONE,
}

interface IFormInput {
name: string;
Expand Down Expand Up @@ -51,6 +62,7 @@ const VeteranServiceRequest: React.FC = () => {
control,
formState: { errors, isValid },
watch,
reset,
} = useForm<IFormInput>();
const [selectedEthnicities, setSelectedEthnicities] = useState<string[]>([]);
const [otherEthnicity, setOtherEthnicity] = useState("");
Expand All @@ -65,6 +77,8 @@ const VeteranServiceRequest: React.FC = () => {

const [pageNumber, setPageNumber] = useState(1);

const [confirmSubmissionModalOpen, setConfirmSubmissionModalOpen] = useState(false);

const numBoys = watch("num_boys");
const numGirls = watch("num_girls");

Expand Down Expand Up @@ -210,7 +224,7 @@ const VeteranServiceRequest: React.FC = () => {
"WY",
];

const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [vsrFormError, setVsrFormError] = useState<VSRFormError>(VSRFormError.NONE);

const [furnitureCategoriesToItems, setFurnitureCategoriesToItems] =
useState<Record<string, FurnitureItem[]>>();
Expand All @@ -221,28 +235,33 @@ const VeteranServiceRequest: React.FC = () => {

const [additionalItems, setAdditionalItems] = useState("");

// Fetch all available furniture items from database
useEffect(() => {
getFurnitureItems()
.then((result) => {
if (result.success) {
setFurnitureCategoriesToItems(
result.data.reduce(
(prevMap: Record<string, FurnitureItem[]>, curItem) => ({
...prevMap,
[curItem.category]: [...(prevMap[curItem.category] ?? []), curItem],
}),
{},
),
);
setErrorMessage(null);
const fetchFurnitureItems = () => {
getFurnitureItems().then((result) => {
if (result.success) {
setFurnitureCategoriesToItems(
result.data.reduce(
(prevMap: Record<string, FurnitureItem[]>, curItem) => ({
...prevMap,
[curItem.category]: [...(prevMap[curItem.category] ?? []), curItem],
}),
{},
),
);
setVsrFormError(VSRFormError.NONE);
} else {
if (result.error === "Failed to fetch") {
setVsrFormError(VSRFormError.CANNOT_RETRIEVE_FURNITURE_NO_INTERNET);
} else {
setErrorMessage("Furniture items not found.");
setVsrFormError(VSRFormError.CANNOT_RETRIEVE_FURNITURE_INTERNAL);
console.error(`Cannot retrieve furniture items: error ${result.error}`);
}
})
.catch((error) => {
setErrorMessage(`An error occurred: ${error.message}`);
});
}
});
};

// Fetch all available furniture items from database
useEffect(() => {
fetchFurnitureItems();
}, []);

// Handle furniture item count whenever a change is made
Expand All @@ -253,7 +272,7 @@ const VeteranServiceRequest: React.FC = () => {
}));
};

const { isMobile } = useScreenSizes();
const { isMobile, isTablet } = useScreenSizes();

// Execute when submit button is pressed
const onSubmit: SubmitHandler<IFormInput> = async (data) => {
Expand Down Expand Up @@ -299,18 +318,17 @@ const VeteranServiceRequest: React.FC = () => {
additionalItems,
};

try {
const response = await createVSR(createVSRRequest);
const response = await createVSR(createVSRRequest);

if (!response.success) {
// TODO: better way of displaying error
throw new Error(`HTTP error! status: ${response.error}`);
if (response.success) {
setConfirmSubmissionModalOpen(true);
} else {
if (response.error === "Failed to fetch") {
setVsrFormError(VSRFormError.CANNOT_SUBMIT_NO_INTERNET);
} else {
setVsrFormError(VSRFormError.CANNOT_SUBMIT_INTERNAL);
console.error(`Cannot submit VSR, error ${response.error}`);
}

// TODO: better way of displaying successful submission (popup/modal)
alert("VSR submitted successfully!");
} catch (error) {
console.error("There was a problem with the fetch operation:", error);
}
};

Expand Down Expand Up @@ -428,6 +446,109 @@ const VeteranServiceRequest: React.FC = () => {
}
};

const renderErrorModal = () => {
switch (vsrFormError) {
case VSRFormError.CANNOT_RETRIEVE_FURNITURE_NO_INTERNET:
return (
<VSRErrorModal
isOpen
onClose={() => {
setVsrFormError(VSRFormError.NONE);
}}
imageComponent={
<Image
src="/errors/no_internet.svg"
alt="No Internet"
width={isMobile ? 100 : isTablet ? 138 : 114}
height={isMobile ? 93 : isTablet ? 129 : 106}
/>
}
title="No Internet Connection"
content="Unable to retrieve the available furniture items due to no internet connection. Please check your connection and try again."
buttonText="Try Again"
onButtonClicked={() => {
setVsrFormError(VSRFormError.NONE);
fetchFurnitureItems();
}}
/>
);
case VSRFormError.CANNOT_RETRIEVE_FURNITURE_INTERNAL:
return (
<VSRErrorModal
isOpen
onClose={() => {
setVsrFormError(VSRFormError.NONE);
}}
imageComponent={
<Image
src="/errors/500_internal_error.svg"
alt="Internal Error"
width={isMobile ? 100 : 155}
height={isMobile ? 69 : 106}
/>
}
title="Internal Error"
content="Something went wrong with retrieving the available furniture items. Our team is working to fix it. Please try again later."
buttonText="Try Again"
onButtonClicked={() => {
setVsrFormError(VSRFormError.NONE);
fetchFurnitureItems();
}}
/>
);
case VSRFormError.CANNOT_SUBMIT_NO_INTERNET:
return (
<VSRErrorModal
isOpen
onClose={() => {
setVsrFormError(VSRFormError.NONE);
}}
imageComponent={
<Image
src="/errors/no_internet.svg"
alt="No Internet"
width={isMobile ? 100 : isTablet ? 138 : 114}
height={isMobile ? 93 : isTablet ? 129 : 106}
/>
}
title="No Internet Connection"
content="Unable to submit the VSR form due to no internet connection. Please check your connection and try again."
buttonText="Try Again"
onButtonClicked={() => {
setVsrFormError(VSRFormError.NONE);
onSubmit(watch());
}}
/>
);
case VSRFormError.CANNOT_SUBMIT_INTERNAL:
return (
<VSRErrorModal
isOpen
onClose={() => {
setVsrFormError(VSRFormError.NONE);
}}
imageComponent={
<Image
src="/errors/500_internal_error.svg"
alt="Internal Error"
width={isMobile ? 100 : 155}
height={isMobile ? 69 : 106}
/>
}
title="Internal Error"
content="Something went wrong with submitting the VSR form. Our team is working to fix it. Please try again later."
buttonText="OK"
onButtonClicked={() => {
setVsrFormError(VSRFormError.NONE);
}}
/>
);
case VSRFormError.NONE:
default:
return null;
}
};

if (pageNumber == 1) {
return (
<div>
Expand Down Expand Up @@ -664,6 +785,7 @@ const VeteranServiceRequest: React.FC = () => {
{renderBottomRow()}
</div>
</form>
{renderErrorModal()}
</div>
);
} else if (pageNumber === 2) {
Expand Down Expand Up @@ -1002,6 +1124,7 @@ const VeteranServiceRequest: React.FC = () => {
{renderBottomRow()}
</div>
</form>
{renderErrorModal()}
</div>
);
} else {
Expand Down Expand Up @@ -1054,8 +1177,15 @@ const VeteranServiceRequest: React.FC = () => {
</div>
</form>
<div className={styles.footer}></div>
{/* TODO: better error handling */}
{errorMessage}
<ConfirmVSRSubmissionModal
isOpen={confirmSubmissionModalOpen}
onClose={() => {
setConfirmSubmissionModalOpen(false);
setPageNumber(1);
reset();
}}
/>
{renderErrorModal()}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Modal } from "@mui/material";
import styles from "@/components/VSRForm/ConfirmVSRSubmissionModal/styles.module.css";
import Image from "next/image";

interface ConfirmVSRSubmissionModalProps {
isOpen: boolean;
onClose: () => unknown;
}

export const ConfirmVSRSubmissionModal = ({ isOpen, onClose }: ConfirmVSRSubmissionModalProps) => {
return (
<Modal open={isOpen} onClose={onClose}>
<div className={styles.root}>
<button onClick={onClose} className={styles.closeButton}>
<Image src="/ic_close_large.svg" alt="close" width={24} height={24} />
</button>
<h2 className={styles.title}>Submitted successfully!</h2>
<p className={styles.content}>
A copy of your submitted VSR form has been sent to your email. We&apos;ll review it
promptly and respond via email as soon as possible. Allow up to 48 business hours to be
contacted for appointment scheduling.
<br></br>
<br></br>
Please check your spam folder if you don&apos;t receive a response within 48 business
hours.
<br></br>
<br></br>
If you need to make changes to your VSR form, submit another VSR form and let us know at{" "}
<a
style={{ textDecorationLine: "underline", fontStyle: "italic" }}
href="[email protected]"
>
[email protected].
</a>
</p>
</div>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.root {
position: absolute;
width: 723px;
background-color: white;
border-radius: 12px;
border: none;
display: flex;
flex-direction: column;
overflow-x: hidden;
overflow-y: hidden;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 64px;
gap: 16px;
border: none !important;
}

.closeButton {
position: absolute;
top: 28px;
right: 28px;
cursor: pointer;
border: none !important;
background: transparent;
}

.title {
color: var(--Secondary-1, #102d5f);
font-family: "Lora";
font-size: 40px;
font-weight: 700;
}

.content {
color: #000;
font-family: "Open Sans";
font-size: 20px;
font-weight: 400;
}

/* tablet version */
@media screen and (max-width: 850px) {
.root {
width: calc(100% - 96px);
}

.title {
font-size: 36px;
}

.content {
font-size: 18px;
}
}

/* mobile version */
@media screen and (max-width: 550px) {
.root {
width: calc(100% - 48px);
padding: 64px 24px;
}

.title {
font-size: 28px;
}

.content {
font-size: 14px;
}
}
Loading

0 comments on commit cc61737

Please sign in to comment.