-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Voting Pages UI #66
base: master
Are you sure you want to change the base?
Voting Pages UI #66
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import axios from "axios"; | ||
import { VoteSelection } from "../util/Types"; | ||
|
||
export const createVote = async ( | ||
vote_id: string, | ||
member_id: string | undefined, | ||
vote_selection: VoteSelection | ||
) => { | ||
const response = await axios.post("api/voting/createVote", { | ||
member_id, | ||
vote_id, | ||
vote_selection, | ||
}); | ||
|
||
return response.data; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import TriangleError from "../assets/TriangleError.svg"; | ||
|
||
export const ErrorComponent = () => { | ||
return ( | ||
<div className="flex flex-col items-center justify-center h-[65vh] lg:h-[100vh] w-full"> | ||
<img src={TriangleError} alt="" /> | ||
<div className="flex flex-col items-center font-sans mt-1 mb-12 gap-2"> | ||
<p className="text-xl text-center max-w-sm"> | ||
Oops! Something went wrong. | ||
</p> | ||
<p className="text-xl text-center max-w-sm"> | ||
This page failed to load. Please try again another time. | ||
</p> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ErrorComponent; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { useMutation } from "@tanstack/react-query"; | ||
import { useState } from "react"; | ||
import { createVote } from "../client/VotingClient"; | ||
import { VoteQuestion, VoteSelection } from "../util/Types"; | ||
|
||
interface QuestionProps { | ||
question: VoteQuestion; | ||
member_id: string | undefined; | ||
} | ||
|
||
export const Question = ({ question, member_id }: QuestionProps) => { | ||
const [selectedVote, setSelectedVote] = useState<VoteSelection>(); | ||
const [error, setError] = useState(false); | ||
const [submitted, setSubmitted] = useState(false); | ||
|
||
const availableOptions = [ | ||
{ value: VoteSelection.YES, label: "Yes" }, | ||
{ value: VoteSelection.NO, label: "No" }, | ||
{ value: VoteSelection.ABSTAIN, label: "Abstain" }, | ||
]; | ||
|
||
const getLabelFromSelection = (value: VoteSelection | undefined) => { | ||
return availableOptions.find((option) => option.value === value)?.label; | ||
}; | ||
|
||
const createVoteMutation = useMutation({ | ||
mutationFn: async (selectedVote: VoteSelection) => { | ||
return createVote(question.uuid, member_id, selectedVote); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh nvm, i see what's happening here. ignore above comment |
||
}, | ||
onSuccess: () => { | ||
setSubmitted(true); | ||
// TODO: this success should update the voting history query however | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what do you mean by this? could we just invalidate the voting history query? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whenever a vote is successfully created it should update the content on the vote record page ( your vote and submssion). The useMutation can force an endpoint to refresh using invalidate query, so I was trying get the endpoint that fetches the votingHistory to refresh, but adding that in was essentially skipping the confirmation message shown at the end of the vote, which isn't ideal. |
||
// this useMutation is setting the data too quickly to the point where | ||
// the submission message doesn't show | ||
}, | ||
}); | ||
|
||
const castVote = async (selectedVote: VoteSelection) => { | ||
createVoteMutation.mutateAsync(selectedVote).then((response) => { | ||
return response; | ||
}); | ||
}; | ||
|
||
return ( | ||
<div className="font-montserrat"> | ||
<div className="flex flex-col"> | ||
<span className="font-bold text-xl">{question.question}</span> | ||
<span className="text-center p-3">{question.description}</span> | ||
</div> | ||
{submitted ? ( | ||
<div className="text-lg pt-10 text-center"> | ||
<span className="font-bold">Thank you for voting. </span> | ||
You voted: {getLabelFromSelection(selectedVote)}. | ||
</div> | ||
) : ( | ||
<div className="flex flex-col"> | ||
<div className="flex flex-col gap-6 pb-10"> | ||
{error && <div>Please input a value before casting vote</div>} | ||
{availableOptions.map((option, index) => { | ||
return ( | ||
<button | ||
className={` border ${ | ||
selectedVote === option.value | ||
? "border-red-700 text-red-700" | ||
: "border-black" | ||
} rounded-md p-1 pl-4 font-bold text-lg font-montserrat text-left`} | ||
value={option.value} | ||
key={index} | ||
onClick={() => { | ||
setError(false); | ||
setSelectedVote(option.value); | ||
}} | ||
> | ||
{option.label} | ||
</button> | ||
); | ||
})} | ||
</div> | ||
<button | ||
className={`${ | ||
selectedVote | ||
? "bg-red-500 text-white" | ||
: "bg-atn-disabled atn-disabled-text border-atn-disabled" | ||
} button-base-red px-4 my-2 w-32 self-end`} | ||
onClick={() => { | ||
if (!selectedVote) { | ||
setError(true); | ||
return; | ||
} else { | ||
castVote(selectedVote); | ||
} | ||
}} | ||
> | ||
{" "} | ||
Confirm | ||
</button> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { VoteHistory } from "../util/Types"; | ||
|
||
interface VoteHistoryProps { | ||
voteRow: VoteHistory; | ||
} | ||
|
||
export const VoteHistoryRow = ({ voteRow }: VoteHistoryProps) => { | ||
return ( | ||
<div className="flex justify-between pb-2"> | ||
<div className="w-10/12 pr-4">{voteRow.question}</div> | ||
<div className="w-10 font-bold pl-4">{voteRow.vote_selection}</div> | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { useQuery } from "@tanstack/react-query"; | ||
import axios from "axios"; | ||
import { VoteQuestion } from "../util/Types"; | ||
|
||
export default function useVoteQuestion(id: string | undefined) { | ||
return useQuery({ | ||
queryKey: ["voting", id], | ||
queryFn: async (): Promise<VoteQuestion[]> => { | ||
const response = await axios.get<VoteQuestion[]>( | ||
`/api/voting/getIfMemberVoted?id=${id}` | ||
); | ||
|
||
return response.data; | ||
}, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { useQuery } from "@tanstack/react-query"; | ||
import axios from "axios"; | ||
import { VoteHistory } from "../util/Types"; | ||
|
||
export default function useVotingRecord(id: string | undefined) { | ||
return useQuery({ | ||
queryKey: ["voting", "history", id], | ||
queryFn: async (): Promise<VoteHistory[]> => { | ||
const response = await axios.get<VoteHistory[]>( | ||
`/api/voting/getVotingRecord?id=${id}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we change this a bit to make the endpoint clearer, i.e. getVotingRecordByMemberId |
||
); | ||
|
||
return response.data; | ||
}, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ import reportWebVitals from "./reportWebVitals"; | |
import { Member } from "./util/Types"; | ||
|
||
// Create a client | ||
const queryClient = new QueryClient(); | ||
export const queryClient = new QueryClient(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why does this need to be exported? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its needed for the (eventual?) invalidateQuery within the useMutation, i.e queryClient.invalidateQuery(queryKey) |
||
|
||
const root = ReactDOM.createRoot( | ||
document.getElementById("root") as HTMLElement | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { ReactElement } from "react"; | ||
import ErrorComponent from "../components/ErrorComponent"; | ||
import Loading from "../components/Loading"; | ||
import { VoteHistoryRow } from "../components/VoteHistoryRow"; | ||
import { useAuth } from "../hooks/useAuth"; | ||
import useVotingRecord from "../hooks/useVotingRecord"; | ||
|
||
export const VotingHistory = (): ReactElement => { | ||
const { member } = useAuth(); | ||
const { | ||
status, | ||
data: votehistory, | ||
error, | ||
isFetching, | ||
} = useVotingRecord(member?.id); | ||
|
||
if (isFetching) { | ||
return <Loading />; | ||
} else if (status === "error") { | ||
return <ErrorComponent />; | ||
} else if (votehistory) { | ||
return ( | ||
<div className="flex justify-center py-10 w-3/4 mx-20 lg:mx-60"> | ||
<div className="flex flex-col space-y-4 min-w-full"> | ||
<div className="flex justify-between"> | ||
<div className="font-bold">Question</div> | ||
<div className="font-bold text-right">Vote</div> | ||
</div> | ||
|
||
{votehistory.length === 0 ? ( | ||
<span> No Voting Records to Display </span> | ||
) : ( | ||
votehistory.map((voteRow) => <VoteHistoryRow voteRow={voteRow} />) | ||
)} | ||
</div> | ||
</div> | ||
); | ||
} else { | ||
return <></>; | ||
} | ||
}; | ||
|
||
export default VotingHistory; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { ReactElement } from "react"; | ||
import ErrorComponent from "../components/ErrorComponent"; | ||
import Loading from "../components/Loading"; | ||
import { Question } from "../components/Question"; | ||
import { useAuth } from "../hooks/useAuth"; | ||
import useVoteQuestion from "../hooks/useVote"; | ||
|
||
export const VotingPage = (): ReactElement => { | ||
const { member } = useAuth(); | ||
const { | ||
status, | ||
data: questions, | ||
error, | ||
isFetching, | ||
} = useVoteQuestion(member?.id); | ||
|
||
if (isFetching) { | ||
return <Loading />; | ||
} else if (status === "error") { | ||
return <ErrorComponent />; | ||
} else if (questions) { | ||
return ( | ||
<div className="flex justify-center w-full p-10"> | ||
{" "} | ||
{questions.length !== 0 ? ( | ||
<Question question={questions[0]} member_id={member?.id} /> | ||
) : ( | ||
<span className="font-bold text-xl pt-10 font-montserrat"> | ||
{" "} | ||
There is currently no live vote. | ||
</span> | ||
)} | ||
</div> | ||
); | ||
} else { | ||
return <></>; | ||
} | ||
}; | ||
|
||
export default VotingPage; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we turn this into a react query hook instead?