diff --git a/backend/modules/project/controller.js b/backend/modules/project/controller.js index 4ac023687..47e860a4b 100644 --- a/backend/modules/project/controller.js +++ b/backend/modules/project/controller.js @@ -213,10 +213,12 @@ controller.exportProjects = async projectIds => { return exportData } -controller.getFinalists = event => { - return Project.find({ _id: { $in: event.finalists } }) +controller.getFinalists = async event => { + const finalistProjects = await Project.find({ + _id: { $in: event.finalists }, + }) + return finalistProjects } -module.exports = controller controller.getDataForPartnerReviewing = async (event, user) => { const data = {} @@ -261,3 +263,5 @@ controller.getDataForPartnerReviewing = async (event, user) => { } return data } + +module.exports = controller diff --git a/backend/modules/project/model.js b/backend/modules/project/model.js index 53754c608..4b45d2ca8 100644 --- a/backend/modules/project/model.js +++ b/backend/modules/project/model.js @@ -6,7 +6,7 @@ const AchievementSchema = require('../../common/schemas/Achievement') const GavelController = require('../reviewing/gavel/controller') const WebhookService = require('../../common/services/webhook') const CustomAnswer = require('@hackjunction/shared/schemas/CustomAnswer') -const ProjectDefaultFields = require('@hackjunction/shared/constants/project-default-fields') +// const ProjectDefaultFields = require('@hackjunction/shared/constants/project-default-fields') // const AnswersSchema = require('@hackjunction/shared/schemas/Answers') const ProjectSchema = new mongoose.Schema({ @@ -70,11 +70,6 @@ const ProjectSchema = new mongoose.Schema({ submissionFormAnswers: { type: [CustomAnswer.mongoose], }, - // TODO default fields - // enabledFields: { - // type: [String], - // default: ProjectDefaultFields, - // }, }) ProjectSchema.set('timestamps', true) diff --git a/backend/modules/winner-votes/controller.js b/backend/modules/winner-votes/controller.js index d5cbc2283..3ab3712f3 100644 --- a/backend/modules/winner-votes/controller.js +++ b/backend/modules/winner-votes/controller.js @@ -1,8 +1,56 @@ const _ = require('lodash') const WinnerVote = require('./model') +const projectController = require('../project/controller') +const tokenVotingController = require('../voting-token/controller') const controller = {} +controller.getFinalistProjectsWithAllVotes = async event => { + const finalistProjects = await projectController.getFinalists(event) + const finalistProjectsWithVotes = finalistProjects.map(project => { + const projectObject = project.toObject() + projectObject.votingData = { + totalVotes: 0, + userVotes: 0, + tokenVotes: 0, + } + return projectObject + }) + const userVotes = await controller.getVotesForEvent(event) + const tokenVotes = await tokenVotingController.getVotesByProject(event._id) + if (userVotes) { + userVotes.map(v => { + const projectWithVotes = _.find( + finalistProjectsWithVotes, + project => project._id.toString() === v.project, + ) + if (projectWithVotes) { + projectWithVotes.votingData.userVotes = v.votes + } + }) + } + if (tokenVotes) { + tokenVotes.map(v => { + const projectWithVotes = _.find( + finalistProjectsWithVotes, + project => project._id.toString() === v.project, + ) + if (projectWithVotes) { + projectWithVotes.votingData.tokenVotes = v.votes + } + }) + } + finalistProjectsWithVotes.forEach(project => { + project.votingData.totalVotes = + project.votingData.userVotes + project.votingData.tokenVotes + }) + const sortedProjects = _.sortBy( + finalistProjectsWithVotes, + n => -1 * n.votingData.totalVotes, + ) + return sortedProjects +} + controller.getVotesForEvent = async event => { const votes = await WinnerVote.find({ event: event._id }).lean() const grouped = _.groupBy(votes, 'project') @@ -14,8 +62,7 @@ controller.getVotesForEvent = async event => { }) return result }, []) - const sorted = _.sortBy(results, n => -1 * n.votes) - return sorted + return results } module.exports = controller diff --git a/backend/modules/winner-votes/routes.js b/backend/modules/winner-votes/routes.js index 7581b8fe8..f7483e10a 100644 --- a/backend/modules/winner-votes/routes.js +++ b/backend/modules/winner-votes/routes.js @@ -12,55 +12,97 @@ const { isEventOrganiser, } = require('../../common/middleware/events') +const votingController = require('./controller') + +const getProjectsWithVotesForEvent = asyncHandler(async (req, res) => { + const finalistProjectsWithAllVotes = + await votingController.getFinalistProjectsWithAllVotes(req.event) + return res.status(200).json(finalistProjectsWithAllVotes) +}) + +const getVote = asyncHandler(async (req, res) => { + const vote = await WinnerVote.findOne({ + event: req.event._id, + user: req.user.sub, + }) + + return res.status(200).json(vote) +}) + +const submitVote = asyncHandler(async (req, res) => { + const vote = await WinnerVote.findOne({ + event: req.event._id, + user: req.user.sub, + }) + if (vote) { + vote.project = req.body.projectId + const result = await vote.save() + return res.status(200).json(result) + } + const newVote = new WinnerVote({ + event: req.event._id, + user: req.user.sub, + project: req.body.projectId, + }) + const result = await newVote.save() + return res.status(200).json(result) +}) + router .route('/:slug') .get( hasToken, hasRegisteredToEvent, - asyncHandler(async (req, res) => { - const vote = await WinnerVote.findOne({ - event: req.event._id, - user: req.user.sub, - }) + getVote, + // asyncHandler(async (req, res) => { + // const vote = await WinnerVote.findOne({ + // event: req.event._id, + // user: req.user.sub, + // }) - return res.status(200).json(vote) - }), + // return res.status(200).json(vote) + // }), ) .post( hasToken, hasRegisteredToEvent, - asyncHandler(async (req, res) => { - const vote = await WinnerVote.findOne({ - event: req.event._id, - user: req.user.sub, - }) - if (vote) { - vote.project = req.body.projectId - const result = await vote.save() - return res.status(200).json(result) - } - const newVote = new WinnerVote({ - event: req.event._id, - user: req.user.sub, - project: req.body.projectId, - }) - const result = await newVote.save() - return res.status(200).json(result) - }), + submitVote, + // asyncHandler(async (req, res) => { + // const vote = await WinnerVote.findOne({ + // event: req.event._id, + // user: req.user.sub, + // }) + // if (vote) { + // vote.project = req.body.projectId + // const result = await vote.save() + // return res.status(200).json(result) + // } + // const newVote = new WinnerVote({ + // event: req.event._id, + // user: req.user.sub, + // project: req.body.projectId, + // }) + // const result = await newVote.save() + // return res.status(200).json(result) + // }), ) router.route('/:slug/results').get( hasToken, isEventOrganiser, - asyncHandler(async (req, res) => { - const votes = await WinnerVote.find({ - event: req.event._id, - }) + getProjectsWithVotesForEvent, + // asyncHandler(async (req, res) => { + // const votes = await WinnerVote.find({ + // event: req.event._id, + // }) - const grouped = _.groupBy(votes, 'project') + // const grouped = _.groupBy(votes, 'project') + // console.log('Grouped votes:', grouped) + // getProjectsWithVotesForEvent(req, res) - return res.status(200).json(grouped) - }), + // return res.status(200).json(grouped) + // } ) +// ) module.exports = router diff --git a/frontend/src/components/inputs/ImageUpload copy/index.js b/frontend/src/components/inputs/ImageUpload copy/index.js deleted file mode 100644 index 2517ba948..000000000 --- a/frontend/src/components/inputs/ImageUpload copy/index.js +++ /dev/null @@ -1,211 +0,0 @@ -import React, { useState, useCallback } from 'react' - -import Upload from 'antd/es/upload' -import { useDispatch, useSelector } from 'react-redux' -import { makeStyles } from '@material-ui/core/styles' -import { Box, Typography, CircularProgress } from '@material-ui/core' -import DeleteIcon from '@material-ui/icons/Delete' -import VisibilityIcon from '@material-ui/icons/Visibility' - -import * as AuthSelectors from 'redux/auth/selectors' -import * as SnackbarActions from 'redux/snackbar/actions' - -const useStyles = makeStyles(theme => ({ - wrapper: { - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - background: '#f7fafc', - }, - emptyWrapper: { - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - cursor: 'pointer', - }, - emptyWrapperText: { - textAlign: 'center', - color: 'black', - userSelect: 'none', - }, - image: ({ resizeMode }) => ({ - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - objectFit: resizeMode || 'contain', - }), - imageOverlay: { - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - background: 'rgba(0,0,0,0.6)', - opacity: 0, - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - transition: 'opacity 0.2s ease', - '&:hover': { - opacity: 1, - }, - cursor: 'pointer', - }, - imageOverlayButton: { - padding: theme.spacing(1), - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - '&:hover': { - background: 'rgba(255,255,255,0.2)', - }, - }, -})) - -export default ({ value, onChange, uploadUrl, resizeMode = 'contain' }) => { - const dispatch = useDispatch() - const idToken = useSelector(AuthSelectors.getIdToken) - const classes = useStyles({ resizeMode }) - const [loading, setLoading] = useState(false) - - if (!uploadUrl) { - throw new Error('ImageUpload component must be supplied an upload url') - } - - const beforeUpload = useCallback( - file => { - const isJpgOrPng = - file.type === 'image/jpeg' || file.type === 'image/png' - if (!isJpgOrPng) { - dispatch( - SnackbarActions.error('Please upload a .jpg or .png file'), - ) - } - const isLt2M = file.size / 1024 / 1024 < 2 - if (!isLt2M) { - dispatch( - SnackbarActions.error( - 'Upload size cannot be more than 2MB', - ), - ) - } - return isJpgOrPng && isLt2M - }, - [dispatch], - ) - - const handleChange = useCallback( - info => { - if (info.file.status === 'uploading') { - setLoading(true) - return - } - if (info.file.status === 'done') { - onChange(info.file.response) - setLoading(false) - } - - if (info.file.status === 'error') { - const message = - info?.file?.response?.message ?? - 'Something went wrong... Please try again' - dispatch(SnackbarActions.error(message)) - setLoading(false) - } - }, - [dispatch, onChange], - ) - - const handleRemove = useCallback( - e => { - e.stopPropagation() - onChange() - }, - [onChange], - ) - - const renderLoading = () => { - return ( - - - - ) - } - const renderImage = () => { - return ( - - upload - - - - - - Remove image - - - window.open(value.url, '_blank')} - > - - - - View original - - - - - ) - } - - const renderEmpty = () => { - return ( - - - Click or drag a file to upload - - - ) - } - - return ( - - - {loading && renderLoading()} - {value && renderImage()} - {!loading && !value && renderEmpty()} - - - ) -} diff --git a/frontend/src/pages/_dashboard/renderDashboard/organiser/projects/winners/index.js b/frontend/src/pages/_dashboard/renderDashboard/organiser/projects/winners/index.js index d8af9b851..c8eb2d7d2 100644 --- a/frontend/src/pages/_dashboard/renderDashboard/organiser/projects/winners/index.js +++ b/frontend/src/pages/_dashboard/renderDashboard/organiser/projects/winners/index.js @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react' -import { Grid, Box, Dialog, Typography } from '@material-ui/core' +import { Grid, Box, Dialog } from '@material-ui/core' import { useSelector } from 'react-redux' import PageHeader from 'components/generic/PageHeader' @@ -11,12 +11,8 @@ import ProjectDetail from 'components/projects/ProjectDetail' import * as OrganiserSelectors from 'redux/organiser/selectors' import * as AuthSelectors from 'redux/auth/selectors' -import EventsService from 'services/events' -//import ProjectsService from 'services/projects' - import WinnerVoteService from 'services/winnerVote' -import VotingTokenService from 'services/votingToken' -import { debugGroup } from 'utils/debuggingTools' +import _ from 'lodash' export default () => { const event = useSelector(OrganiserSelectors.event) @@ -26,65 +22,35 @@ export default () => { const [selected, setSelected] = useState(false) const [projects, setProjects] = useState([]) - const [results, setResults] = useState({}) - const [tokenVoterResults, setTokenVoterResults] = useState([]) - - const updateVote = useCallback(() => { - return WinnerVoteService.getResults(idToken, event.slug) - }, [idToken, event]) - - const updateVotesWithToken = useCallback(() => { - return VotingTokenService.getVotingTokenResults( - idToken, - event.slug, - ).catch(error => { - console.error(error?.response) - }) - }, [idToken, event]) - - // const updateProjects = useCallback(() => { - // // TODO use EventsService - // return ProjectsService.getProjectsByEvent(event.slug) - // // return EventsService.getWinnerProjects(idToken, event.slug) - // }, [event]) const update = useCallback(async () => { - setLoading(true) - if (event.overallReviewMethod === 'finalsManualSelection') { - const vote = await updateVote() - const partnerVotes = await updateVotesWithToken() - const topProjects = await EventsService.getFinalists( - idToken, - event.slug, - ) - setProjects(topProjects) - if (vote) { - setResults(vote) - } - if (partnerVotes) { - setTokenVoterResults(partnerVotes) + try { + setLoading(true) + const finalistProjectsWithAllVotes = + await WinnerVoteService.getResults(idToken, event.slug) + if ( + Array.isArray(finalistProjectsWithAllVotes) && + finalistProjectsWithAllVotes.length > 0 + ) { + setProjects(finalistProjectsWithAllVotes) } + } catch (error) { + console.error(error) + } finally { + setLoading(false) } - setLoading(false) - }, [ - event.overallReviewMethod, - event.slug, - updateVote, - updateVotesWithToken, - idToken, - ]) + }, [event, idToken]) useEffect(() => { - update() - }, [update]) - - const getScoreText = projectId => { - const scoreFromUsers = results[projectId]?.length ?? 0 - const scoreFromTokenVoters = - tokenVoterResults.find(res => res.project === projectId)?.votes ?? 0 - - const total = scoreFromUsers + scoreFromTokenVoters + if (event?.slug) { + update() + } + }, [event]) + const getScoreText = project => { + const scoreFromUsers = project?.votingData?.userVotes ?? 0 + const scoreFromTokenVoters = project?.votingData?.tokenVotes ?? 0 + const total = project?.votingData?.totalVotes ?? 0 return ( <> Total votes received: {total}
@@ -93,16 +59,16 @@ export default () => { ) } - debugGroup('Results', results) return ( - {projects.map(project => ( + {projects.map((project, index) => ( setSelected(project)} showScore={true} /> diff --git a/frontend/src/pages/_dashboard/renderDashboard/participant/finalist-voting/index.js b/frontend/src/pages/_dashboard/renderDashboard/participant/finalist-voting/index.js index 4272a9e3d..107bfde2d 100644 --- a/frontend/src/pages/_dashboard/renderDashboard/participant/finalist-voting/index.js +++ b/frontend/src/pages/_dashboard/renderDashboard/participant/finalist-voting/index.js @@ -13,10 +13,7 @@ import * as DashboardSelectors from 'redux/dashboard/selectors' import * as AuthSelectors from 'redux/auth/selectors' import * as SnackbarActions from 'redux/snackbar/actions' -import * as OrganiserSelectors from 'redux/organiser/selectors' - import EventsService from 'services/events' -import ProjectsService from 'services/projects' import WinnerVoteService from 'services/winnerVote' @@ -32,97 +29,75 @@ export default () => { const [vote, setVote] = useState(null) const [hasVoted, setVoted] = useState(false) - const updateVote = useCallback(() => { + const getCurrentVote = useCallback(() => { return WinnerVoteService.getVote(idToken, event.slug) }, [idToken, event]) - const updateProjects = useCallback(() => { - // TODO use EventsService - return ProjectsService.getProjectsByEvent(event.slug) - // return EventsService.getWinnerProjects(idToken, event.slug) - }, [event]) - - const update = useCallback(async () => { + const getFinalists = useCallback(async () => { setLoading(true) - if (event.overallReviewMethod === 'finalsManualSelection') { - const vote = await updateVote() - - const topProjects = await EventsService.getFinalists( - idToken, - event.slug, - ) - setProjects(topProjects) - if (vote) { - setVote(vote.project) - setVoted(true) - } - } else { - //TODO holy shit redo this - const topProjects = [] - const rankingsByTrack = useSelector( - OrganiserSelectors.rankingsByTrack, - ) - try { - const [vote, projects] = await Promise.all([ - updateVote(), - updateProjects(), - ]) - if (rankingsByTrack) { - Object.keys(rankingsByTrack).forEach(name => - topProjects?.push( - projects?.find( - x => x._id === rankingsByTrack[name][0], - ), - ), - ) - } - setProjects(topProjects) - if (vote) { - setVote(vote.project) - } - } catch (err) { + EventsService.getFinalists(idToken, event.slug) + .then(finalistProjects => { + setProjects(finalistProjects) + }) + .catch(err => { dispatch( SnackbarActions.error( - 'Oops, something went wrong... Please reload the page.', + 'Something went wrong... Please try again', ), ) - } - } - setLoading(false) - }, [ - event.overallReviewMethod, - event.slug, - updateVote, - idToken, - updateProjects, - dispatch, - ]) + console.error(err) + }) + .finally(() => { + setLoading(false) + }) + }, [idToken, event]) useEffect(() => { + getFinalists() update() - }, [update]) + }, []) + + const update = useCallback(async () => { + try { + setLoading(true) + const vote = await getCurrentVote() + if (vote) { + setVote(vote.project) + setVoted(true) + } + } catch (err) { + dispatch( + SnackbarActions.error( + 'Something went wrong... Please try again', + ), + ) + console.error(err) + } finally { + setLoading(false) + } + }, [event, idToken]) const handleSubmit = useCallback(async () => { - setLoading(true) try { + setLoading(true) const result = await WinnerVoteService.submitVote( idToken, event.slug, vote, ) - setVote(result.project) - setVoted(true) - dispatch(SnackbarActions.success('Vote submitted!')) + if (result) { + dispatch(SnackbarActions.success('Vote submitted!')) + } } catch (err) { dispatch( SnackbarActions.error( 'Something went wrong... Please try again', ), ) + } finally { + setLoading(false) } - setLoading(false) - }, [idToken, event.slug, vote, dispatch]) - + }, [idToken, event, vote]) return ( {
- {projects.map(project => ( + {projects.map((project, index) => ( setSelected(project)}