Skip to content

Commit

Permalink
Merge pull request #91 from ChangePlusPlusVandy/volunteer-application…
Browse files Browse the repository at this point in the history
…-form

feature/volunteer-application-front-end
  • Loading branch information
JiashuHarryHuang committed Apr 27, 2024
2 parents 626b9db + 9c3d193 commit 7e0fbac
Show file tree
Hide file tree
Showing 8 changed files with 520 additions and 202 deletions.
106 changes: 87 additions & 19 deletions components/Event/Event.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { QueriedVolunteerEventDTO } from 'bookem-shared/src/types/database';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import Header from '@/components/Event/Header';
import BookIcon from '@/components/Event/BookIcon';
import EventName from '@/components/Event/EventName';
Expand All @@ -16,6 +16,9 @@ import {
import { Media } from '@/lib/media';
import Footer from '@/components/Event/Footer';
import { BOOKEM_THEME } from '@/utils/constants';
import { message } from 'antd';
import ApplicationPopup from './EventApplication/ApplicationPopup';

/**
* Event Detail
* @param event Data about the event
Expand All @@ -26,46 +29,95 @@ const Event = ({ event }: { event: QueriedVolunteerEventDTO }) => {
* False: display Contact
*/
const [showAbout, setShowAbout] = useState<boolean>(true);
const [showApplication, setShowApplication] = useState<boolean>(false);
const handleShowAbout = () => !showAbout && setShowAbout(!showAbout);
const handleShowContact = () => showAbout && setShowAbout(!showAbout);

/**
* Keep track of whether this event is signed up or not
*/
const [signedUp, setSignedUp] = useState(false);

/**
* keep track of the fetched application's reponse when the event requires application
*/
const [applicationResponse, setApplicationResponse] = useState(null);

useEffect(() => {
const fetchApplicationStatus = async () => {
// Fetch the submitted application
const response = await fetch(
`/api/event/${event._id}/submitted-application`,
{
method: 'GET',
}
);

// console.log("response: ", response.status)

// if the application is not found, it means the user has not applied to the event
// do nothing
if (response.status === 404 || response.status === 400) {
return;
}

const { application: eventApplication, response: applicationResponse } =
await response.json();

// console.log("application: ", eventApplication)
// console.log("response: ", applicationResponse)

// If the application is submitted, set signedUp to true
setSignedUp(true);

// set the application response
setApplicationResponse(applicationResponse);
};

if (event.applicationId != null) {
fetchApplicationStatus();
}
}, [event._id, event.applicationId]);

/**
* Sign up/Unsign up the current user to the event
* @param event
*/
const signUpEvent = async () => {
try {
// If the event requires application, redirect to application page
if (event.requireApplication) {
// TODO: redirect to event application page
alert('Go to event application!');
if (event.applicationId != null) {
if (signedUp) {
message.info('You have already applied to this event');
return;
}
setShowApplication(true);
// console.log("application questions: ", event.applicationId)

return;
}
} else {
// Send POST request to sign up
const response = await fetch('/api/event/' + event._id, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});

// Send POST request to sign up
const response = await fetch('/api/event/' + event._id, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});

// Success
if (response.status === 200) {
const message = await response.json();
console.log(message);
// Update sign up state
setSignedUp(!signedUp);
// Success
if (response.status === 200) {
const message = await response.json();
// console.log(message);
// Update sign up state
setSignedUp(!signedUp);
}
}
} catch (error) {
console.error(error);
}
};

console.log('event: ', event);
return (
<>
<EventBox>
Expand All @@ -82,6 +134,7 @@ const Event = ({ event }: { event: QueriedVolunteerEventDTO }) => {
setSignedUp={setSignedUp}
event={event}
signUpEvent={signUpEvent}
applicationResponses={applicationResponse}
/>
</MiddleBox>

Expand Down Expand Up @@ -142,6 +195,21 @@ const Event = ({ event }: { event: QueriedVolunteerEventDTO }) => {
signUpEvent={signUpEvent}
/>
</Media>

{/* Application Popup */}
{event.applicationId != null && (
<ApplicationPopup
event={event}
visible={showApplication}
onClose={() => {
setShowApplication(false);
}}
onSubmit={() => {
message.success('Application Submitted');
setSignedUp(true);
}}
/>
)}
</>
);
};
Expand Down
130 changes: 130 additions & 0 deletions components/Event/EventApplication/ApplicationPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Modal } from 'antd';
import 'survey-core/defaultV2.min.css';
import { Model } from 'survey-core';
import { Survey } from 'survey-react-ui';
import { useState, useEffect } from 'react';
import {
ApplicationQuestionData,
ApplicationAnswer,
QueriedVolunteerEventDTO,
} from 'bookem-shared/src/types/database';

// choices can type string[] or null
const processChoices = (choices: String[] | undefined) => {
if (choices == null) {
return null;
}

return choices.map(choice => {
return {
value: choice,
text: choice,
};
});
};

// process answes to fit the answer type. return the answer casted into application answer
const processAnswers = (answerObj: any) => {
// the key of the answer object is the question id
// the value is the answer. The value can be a string or an array of strings
// we need to convert this into an array of application answers
const answers = Object.keys(answerObj).map(key => {
const answer = answerObj[key];
return {
questionId: key,
// check if the answer is an array, if not convert it into an array
text: Array.isArray(answer) ? answer : [answer],
};
});

return answers;
};

const makeSurveyModel = (questions: ApplicationQuestionData[]) => {
const elements = questions.map(question => {
return {
name: question._id,
title: question.title,
type: question.type,
isRequired: question.isRequired,
choices: processChoices(question.choices),
};
});

return {
elements: elements,
};
};

export default function ApplicationPopup({
visible,
onClose,
event,
onSubmit,
}: {
visible: boolean;
onClose: () => void;
event: QueriedVolunteerEventDTO;
onSubmit: () => void;
}) {
const { name } = event;
const [questions, setQuestions] = useState<ApplicationQuestionData[]>([]);

let survey = new Model(makeSurveyModel(questions));

survey.onComplete.add(result => {
const answers = processAnswers(result.data);
console.log('Survey results: ', answers);
// send the answers to the server
fetch('/api/event/' + event._id + '/apply', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
answers: answers,
}),
});

onClose();
onSubmit();
});

useEffect(() => {
const fetchQuestions = async () => {
const res = await fetch('/api/event/' + event._id + '/apply', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

// no application questions found
if (res.status === 404) {
return;
}

const data = await res.json();
const questions = data.message;
setQuestions(questions);
};
// fetch the questions from the database
// set the survey model
fetchQuestions();
}, [event._id]);

return (
<Modal
width={800}
// title="Application"
open={visible}
onCancel={onClose}
footer={null}>
<h2>
Application for event: <br />
<span style={{ color: 'gray', fontSize: '1rem' }}>{name}</span>
</h2>
<Survey model={survey} />
</Modal>
);
}
78 changes: 78 additions & 0 deletions components/Event/EventApplication/ApplicationStatusDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { StatusDisplayText } from '@/styles/components/Event/eventName.styles';
import {
CheckCircleOutlined,
ReadOutlined,
ExclamationCircleOutlined,
} from '@ant-design/icons';
import { useState, useEffect } from 'react';
import {
ApplicationStatus,
ApplicationResponseData,
QueriedVolunteerEventDTO,
} from 'bookem-shared/src/types/database';

const ApprovedText = () => (
<span>
<CheckCircleOutlined
style={{
color: 'green',
margin: '0 10px',
}}
/>
Approved
</span>
);

const PendingText = () => (
<span>
<ReadOutlined
style={{
color: 'orange',
margin: '0 10px',
}}
/>
Under Review
</span>
);

const RejectedText = () => (
<span>
<ExclamationCircleOutlined
style={{
color: 'red',
margin: '0 10px',
}}
/>
Rejected
</span>
);

const statusDisplayText = {
[ApplicationStatus.Pending]: <PendingText />,
[ApplicationStatus.Approved]: <ApprovedText />,
[ApplicationStatus.Rejected]: <RejectedText />,
};

// Display the status of the volunteer application
// TODO: add logic for fetching application responses and display its status
const ApplicationStatusDisplay = ({
event,
applicationResponses,
}: {
event: QueriedVolunteerEventDTO;
applicationResponses: ApplicationResponseData | null;
}) => {
if (applicationResponses?.status == null) {
return <StatusDisplayText>Application Required</StatusDisplayText>;
}

return (
<StatusDisplayText>
Application Status:
<br />
{statusDisplayText[applicationResponses.status]}
</StatusDisplayText>
);
};

export default ApplicationStatusDisplay;
Loading

0 comments on commit 7e0fbac

Please sign in to comment.