From 49c52d822e428118d643a5afc207ef1e90f5567c Mon Sep 17 00:00:00 2001 From: Belle Date: Thu, 22 Aug 2024 16:55:54 +0800 Subject: [PATCH] add more pages to vote :) --- components/arcade/showcase/project-view.js | 4 +- pages/arcade/showcase/my.js | 3 +- pages/arcade/showcase/vote.js | 1147 +++++++++++++------- 3 files changed, 760 insertions(+), 394 deletions(-) diff --git a/components/arcade/showcase/project-view.js b/components/arcade/showcase/project-view.js index 19dffd3c..0135b7d0 100644 --- a/components/arcade/showcase/project-view.js +++ b/components/arcade/showcase/project-view.js @@ -119,8 +119,8 @@ const ProjectView = ({ }} >

{title}

-

{description}

-

By {user}

+

{description != 'Description Not Found' ? description : <>}

+

{user != 'User Not Found' ? <>By {user} : <>}

{ return (
) -const My = () => { - // let originalProjects = { - // cohort: { - // id: 'rectAjJ2Lv4dDhUGR' - // }, - // showcases: [ - // { - // id: 'rec4cl4TSvfwwnU6H', - // createdTime: '2024-08-14T13:47:49.000Z', - // fields: { - // Name: 'Blast-off', - // 'Code Link': 'https://github.com/Ranger-NF/BlastOff', - // 'Play Link': 'https://ranger-nf.itch.io/blastoff', - // Description: 'This is a description', - // color: '#14f0cb', - // textColor: '#ffffff', - // ScreenshotLink: - // 'https://cloud-c6ul4axwx-hack-club-bot.vercel.app/0instagram_profile_downloader.jpg', - // ReadMeLink: - // 'https://raw.githubusercontent.com/remarkjs/react-markdown/main/readme.md', - // 'View link': - // 'https://hackclub.com/arcade/showcase/project/rec4cl4TSvfwwnU6H' - // } - // }, - // { - // id: 'recLyu8vD4mmUqYrA', - // createdTime: '2024-08-19T17:12:16.000Z', - // fields: { - // Name: 'Turtle T1 (A rabbit R1 spinoff)', - // 'Code Link': 'https://github.com/briyandyju09/Turtle-T1', - // Description: 'A web based spin off to the not so liked rabbit R1', - // color: '#FAEFD6', - // ScreenshotLink: - // 'https://cloud-k7p388c60-hack-club-bot.vercel.app/0image-19.png', - // ReadMeLink: - // 'https://raw.githubusercontent.com/briyandyju09/Turtle-T1/main/README.md', - // 'View link': - // 'https://hackclub.com/arcade/showcase/project/recLyu8vD4mmUqYrA' - // } - // }, - // { - // id: 'recYZd9J9MS4fDuJC', - // createdTime: '2024-08-16T22:28:10.000Z', - // fields: { - // Name: 'peace-and-tranquility', - // 'Code Link': 'https://github.com/maxwofford/peace-and-tranquility', - // 'Play Link': 'https://dinosaurbbq.org/', - // Description: 'No one is around to help', - // color: '#293438', - // textColor: '#ffffff', - // ScreenshotLink: - // 'https://cloud-c6ul4axwx-hack-club-bot.vercel.app/0instagram_profile_downloader.jpg', - // ReadMeLink: - // 'https://raw.githubusercontent.com/remarkjs/react-markdown/main/readme.md', - // 'View link': - // 'https://hackclub.com/arcade/showcase/project/recYZd9J9MS4fDuJC' - // } - // }, - // { - // id: 'reclIN8evh60EH90v', - // createdTime: '2024-08-17T05:57:05.000Z', - // fields: { - // Name: 'site2eeeEEE534', - // 'Code Link': 'https://github.com/hackclub/site', - // 'Play Link': 'https://hackclub.com', - // Description: - // '🌈 The new, new Hack Club website (uses Next.js & Theme UI).', - // color: '#68d0f8', - // textColor: '#fafafa', - // ScreenshotLink: - // 'https://cloud-c6ul4axwx-hack-club-bot.vercel.app/0instagram_profile_downloader.jpg', - // ReadMeLink: - // 'https://raw.githubusercontent.com/remarkjs/react-markdown/main/readme.md', - // 'View link': - // 'https://hackclub.com/arcade/showcase/project/reclIN8evh60EH90v' - // } - // }, - // { - // id: 'reclIN8evh60EH2220v', - // createdTime: '2024-08-17T05:57:05.000Z', - // fields: { - // Name: 'site2eeeEEE53344', - // 'Code Link': 'https://github.com/hackclub/site', - // 'Play Link': 'https://hackclub.com', - // Description: - // '🌈 The new, new Hack Club website (uses Next.js & Theme UI).', - // color: '#68d0f8', - // textColor: '#fafafa', - // ScreenshotLink: - // 'https://cloud-c6ul4axwx-hack-club-bot.vercel.app/0instagram_profile_downloader.jpg', - // ReadMeLink: - // 'https://raw.githubusercontent.com/remarkjs/react-markdown/main/readme.md', - // 'View link': - // 'https://hackclub.com/arcade/showcase/project/reclIN8evh60EH90v' - // } - // } - // ] - // } +//CREDIT: https://www.joshwcomeau.com/snippets/react-hooks/use-mouse-position/ +const useMousePosition = () => { + const [mousePosition, setMousePosition] = useState({ x: null, y: null }) - // originalProjects = originalProjects.showcases - const [projects, setProjects] = useState([]) - const [originalProjects, setOriginalProjects] = useState([]) - const [name, setName] = useState('') - const [loadStatus, setLoadStatus] = useState('loading') - const [status, setStatus] = useState('loading') - const [errorMsg, setError] = useState(null) - const [votes, setVotes] = useState({}) + useEffect(() => { + const updateMousePosition = ev => { + setMousePosition({ x: ev.clientX, y: ev.clientY }) + } + + window.addEventListener('mousemove', updateMousePosition) + + return () => { + window.removeEventListener('mousemove', updateMousePosition) + } + }, []) + + return mousePosition +} + +const Vote = () => { + /* test data */ + let originalProjects = { + cohort: { + id: 'rectAjJ2Lv4dDhUGR' + }, + showcases: [ + { + id: 'rec4cl4TSvfwwnU6H', + createdTime: '2024-08-14T13:47:49.000Z', + fields: { + Name: 'Blast-off', + 'Code Link': 'https://github.com/Ranger-NF/BlastOff', + 'Play Link': 'https://ranger-nf.itch.io/blastoff', + Description: 'This is a description', + color: '#14f0cb', + textColor: '#ffffff', + ScreenshotLink: + 'https://cloud-c6ul4axwx-hack-club-bot.vercel.app/0instagram_profile_downloader.jpg', + ReadMeLink: + 'https://raw.githubusercontent.com/remarkjs/react-markdown/main/readme.md', + 'View link': + 'https://hackclub.com/arcade/showcase/project/rec4cl4TSvfwwnU6H' + } + }, + { + id: 'recLyu8vD4mmUqYrA', + createdTime: '2024-08-19T17:12:16.000Z', + fields: { + Name: 'Turtle T1 (A rabbit R1 spinoff)', + 'Code Link': 'https://github.com/briyandyju09/Turtle-T1', + Description: 'A web based spin off to the not so liked rabbit R1', + color: '#FAEFD6', + ScreenshotLink: + 'https://cloud-k7p388c60-hack-club-bot.vercel.app/0image-19.png', + ReadMeLink: + 'https://raw.githubusercontent.com/briyandyju09/Turtle-T1/main/README.md', + 'View link': + 'https://hackclub.com/arcade/showcase/project/recLyu8vD4mmUqYrA' + } + }, + { + id: 'recYZd9J9MS4fDuJC', + createdTime: '2024-08-16T22:28:10.000Z', + fields: { + Name: 'peace-and-tranquility', + 'Code Link': 'https://github.com/maxwofford/peace-and-tranquility', + 'Play Link': 'https://dinosaurbbq.org/', + Description: 'No one is around to help', + color: '#293438', + textColor: '#ffffff', + ScreenshotLink: + 'https://cloud-c6ul4axwx-hack-club-bot.vercel.app/0instagram_profile_downloader.jpg', + ReadMeLink: + 'https://raw.githubusercontent.com/remarkjs/react-markdown/main/readme.md', + 'View link': + 'https://hackclub.com/arcade/showcase/project/recYZd9J9MS4fDuJC' + } + }, + { + id: 'reclIN8evh60EH90v', + createdTime: '2024-08-17T05:57:05.000Z', + fields: { + Name: 'site2eeeEEE534', + 'Code Link': 'https://github.com/hackclub/site', + 'Play Link': 'https://hackclub.com', + Description: + '🌈 The new, new Hack Club website (uses Next.js & Theme UI).', + color: '#68d0f8', + textColor: '#fafafa', + ScreenshotLink: + 'https://cloud-c6ul4axwx-hack-club-bot.vercel.app/0instagram_profile_downloader.jpg', + ReadMeLink: + 'https://raw.githubusercontent.com/remarkjs/react-markdown/main/readme.md', + 'View link': + 'https://hackclub.com/arcade/showcase/project/reclIN8evh60EH90v' + } + }, + { + id: 'reclIN8evh60EH2220v', + createdTime: '2024-08-17T05:57:05.000Z', + fields: { + Name: 'site2eeeEEE53344', + 'Code Link': 'https://github.com/hackclub/site', + 'Play Link': 'https://hackclub.com', + Description: + '🌈 The new, new Hack Club website (uses Next.js & Theme UI).', + color: '#68d0f8', + textColor: '#fafafa', + ScreenshotLink: + 'https://cloud-c6ul4axwx-hack-club-bot.vercel.app/0instagram_profile_downloader.jpg', + ReadMeLink: + 'https://raw.githubusercontent.com/remarkjs/react-markdown/main/readme.md', + 'View link': + 'https://hackclub.com/arcade/showcase/project/reclIN8evh60EH90v' + } + } + ] + } + originalProjects = originalProjects.showcases + + /* necessary to make drag and drop work */ + const [showUIElements, setShowUIElements] = useState(false) + + /* get projects */ + const projectCount = Object.keys(originalProjects).length + const [projects, setProjects] = useState(originalProjects) + + /* for showing individual projects */ const [openProjectId, setOpenProjectId] = useState('') const [openProject, setOpenProject] = useState([]) - const [showUIElements, setShowUIElements] = useState(false) - const [activeDroppableId, setActiveDroppableId] = useState(null) - const [activeDroppable, setActiveDroppable] = useState(true) + /* status variables */ + const [loadStatus, setLoadStatus] = useState('loading') + const [status, setStatus] = useState('loading') const [submitStatus, setSubmitStatus] = useState('loading') + + /* collect votes */ const [creative, setCreative] = useState([]) const [technical, setTechnical] = useState([]) const [overall, setOverall] = useState([]) + /* change what is shown on page */ const [showCreative, setShowCreative] = useState(true) const [showTechnical, setShowTechnical] = useState(false) const [showOverall, setShowOverall] = useState(false) const [endPage, setEndPage] = useState(false) const [isButtonActive, setIsButtonActive] = useState(false) + const [startVote, setStartVote] = useState(false) + const [startViewProject, setStartViewProject] = useState(false) + const [currentView, setCurrentView] = useState(0) + /* drag and drop logic */ + const [activeDroppableId, setActiveDroppableId] = useState(null) + const [activeDroppable, setActiveDroppable] = useState(true) + const dragItemRef = useRef(null) + const [dragging, setDragging] = useState(false) + const [votes, setVotes] = useState({}) + const [voted, setVoted] = useState(false) + /* check if it's loaded */ + useEffect(() => { + setShowUIElements(true) + if (typeof document !== 'undefined') { + let vote = window.localStorage.getItem('arcade.voted') + + if (vote == 'true') { + setVoted(true) + } + } + }, []) + + /* load projects */ const loadProjects = async () => { const token = window.localStorage.getItem('arcade.authToken') const response = await fetch('/api/arcade/showcase/cohort/active', { @@ -217,7 +265,6 @@ const My = () => { }).catch(e => { console.error(e) setLoadStatus('error') - setError(e) }) const data = await response.json() if (data.error) { @@ -230,16 +277,17 @@ const My = () => { } } - useEffect(async () => { - loadProjects() - }, []) + // useEffect(async () => { + // loadProjects() + // }, []) + /* get individual project details */ const getProjectDetails = async () => { const token = window.localStorage.getItem('arcade.authToken') setStatus('loading') try { - if (openProjectId){ + if (openProjectId) { const response = await fetch( `/api/arcade/showcase/projects/${openProjectId}`, { @@ -249,12 +297,12 @@ const My = () => { } } ) - + const data = await response.json() - + if (data.error) { + console.log(data.error) setStatus('error') - setError(data.error) } else { setOpenProject(data.project) setStatus('success') @@ -263,7 +311,6 @@ const My = () => { } catch (e) { console.error(e) setStatus('error') - setError(e.message) } } @@ -271,66 +318,113 @@ const My = () => { getProjectDetails() }, [openProjectId]) + /* HANDLE DRAG AND DROP LOGIC BELOW*/ + const [mousePosition, setMousePosition] = useState({ x: null, y: null }) + useEffect(() => { - setShowUIElements(true) + const updateMousePosition = ev => { + setMousePosition({ x: ev.clientX, y: ev.clientY }) + } + + window.addEventListener('mousemove', updateMousePosition) + + return () => { + window.removeEventListener('mousemove', updateMousePosition) + } }, []) - useEffect(() => { - if (Object.keys(votes).length == 5) { - setIsButtonActive(true) - } else { - setIsButtonActive(false) - } - }, [votes]) - - const extractVotes = votes => { - const sortedKeys = Object.keys(votes).sort((a, b) => { - const numA = parseInt(a.split('-')[1], 10) - const numB = parseInt(b.split('-')[1], 10) - return numA - numB - }) - - return sortedKeys.map(key => votes[key]) + const isCursorInsideBoundingBox = (cursorPosition, boundingBox) => { + const { x, y, width, height } = boundingBox + return ( + cursorPosition.x >= x && + cursorPosition.x <= x + width && + cursorPosition.y >= y && + cursorPosition.y <= y + height + ) } - const onDragUpdate = update => { - setActiveDroppable(true) - const { destination } = update - if (destination) { - setActiveDroppableId(destination.droppableId) + const getBoundingBoxes = () => { + if (typeof document === 'undefined') { + return [] } + const droppableIds = ['votes-1', 'votes-2', 'votes-3', 'votes-4', 'votes-5'] + + const boundingBoxes = droppableIds + .map(id => { + const element = document.getElementById(id) + if (element) { + const rect = element.getBoundingClientRect() + return { + id, + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height + } + } + return null + }) + .filter(box => box !== null) + + return boundingBoxes + } + + const boundingBoxes = getBoundingBoxes() + + useEffect(() => { + if (dragging) { + for (const box of boundingBoxes) { + if ( + box.id.startsWith('votes') && + isCursorInsideBoundingBox(mousePosition, box) + ) { + console.log(box.id) + setActiveDroppable(true) + setActiveDroppableId(box.id) + } + } + } + }, [mousePosition]) + + const onDragUpdate = update => { + setDragging(true) } const onDragEnd = result => { + setDragging(false) const { source, destination } = result - if (!destination) return + let destinationId = '' - if ( - source.droppableId === 'projects' && - destination.droppableId.startsWith('votes-') - ) { - const linkId = result.draggableId - const voteId = destination.droppableId + for (const box of boundingBoxes) { + if ( + box.id.startsWith('votes') && + isCursorInsideBoundingBox(mousePosition, box) + ) { + destinationId = box.id + setActiveDroppable(false) - setVotes(prevVotes => { - const updatedVotes = { - ...prevVotes, - [voteId]: linkId - } + const linkId = result.draggableId + const voteId = destinationId - const votedProjectIds = Object.values(updatedVotes) + setVotes(prevVotes => { + const updatedVotes = { + ...prevVotes, + [voteId]: linkId + } - const updatedProjects = originalProjects.filter( - project => !votedProjectIds.includes(project.id) - ) + const votedProjectIds = Object.values(updatedVotes) - setProjects(updatedProjects) + const updatedProjects = originalProjects.filter( + project => !votedProjectIds.includes(project.id) + ) - return updatedVotes - }) + setProjects(updatedProjects) + + return updatedVotes + }) + } } - setActiveDroppable(false) } const deleteVote = voteId => { @@ -352,7 +446,7 @@ const My = () => { } const renderVoteBox = voteId => { - const linkId = votes[voteId] + const linkId = votes[voteId] //checks if it was voted const project = originalProjects.find(link => link.id === linkId) if (project) { @@ -445,6 +539,25 @@ const My = () => { ) } + /* VOTE SUBMISSION BELOW */ + useEffect(() => { + if (Object.keys(votes).length == 5) { + setIsButtonActive(true) + } else { + setIsButtonActive(true) + } + }, [votes]) + + const extractVotes = votes => { + const sortedKeys = Object.keys(votes).sort((a, b) => { + const numA = parseInt(a.split('-')[1], 10) + const numB = parseInt(b.split('-')[1], 10) + return numA - numB + }) + + return sortedKeys.map(key => votes[key]) + } + const voteCreative = () => { let ids = extractVotes(votes) setCreative(ids) @@ -472,43 +585,56 @@ const My = () => { setEndPage(true) } - const submitVote = async (overall, technical, creative) => { - const authToken = window.localStorage.getItem('arcade.authToken'); + const submitVote = async (overall, technical, creative) => { + const authToken = window.localStorage.getItem('arcade.authToken') - try { - const response = await fetch('/api/arcade/showcase/vote', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Bearer "+ authToken, - }, - body: JSON.stringify({ - overall, - technical, - creative, - }), - }) + try { + const response = await fetch('/api/arcade/showcase/vote', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + authToken + }, + body: JSON.stringify({ + overall, + technical, + creative + }) + }) - - const data = await response.json(); - setSubmitStatus('success') - return data; - } catch (error) { - console.error('Error submitting vote:', error); - setSubmitStatus('error') - throw error; - } - }; + const data = await response.json() + if (data.error) { + console.error('Error submitting vote:', data.error) + setSubmitStatus('error') + } else { + console.log(data) + setSubmitStatus('success') + localStorage.setItem('arcade.voted', 'true') + jsConfetti.current.addConfetti({ + confettiColors: ['#09AFB4', '#FF5C00'] + }) + } + } catch (error) { + console.error('Error submitting vote:', error) + setSubmitStatus('error') + throw error + } + } - const isFirstRender = useRef(true); + let jsConfetti = useRef() useEffect(() => { - if (overall.lengh > 0 && technical.length > 0 && creative.length > 0) { //skips first render + jsConfetti.current = new JSConfetti() + }, []) + + useEffect(() => { + if (overall.length > 0 && technical.length > 0 && creative.length > 0) { + //skips first render submitVote(overall, technical, creative) } }, [endPage]) - return endPage == true ? ( + return voted == true ? (
{ alignItems: 'center', height: '100vh', width: '100vw', - backgroundColor: '#FAEFD6' + backgroundColor: '#FAEFD6', + color: '#35290F', + flexDirection: 'column', + gap: '20px', + position: 'relative', + textAlign: 'center' }} > - {submitStatus == 'loading' - ? 'Loading' - : submitStatus == 'success' - ? 'Thanks for voting!' - : 'Ran into an error sending your votes'} + + + You've already voted. + + + If this is a mistake, please send a message to #arcade-help + + +
- ) : ( - showUIElements && ( -
-
+ + - -
+ {submitStatus == 'loading' + ? 'Loading...' + : submitStatus == 'success' + ? 'Thanks for voting!' + : 'Ran into an error sending your votes'} + + + +
+ ) : ( + showUIElements && ( +
+
+
- - Ships - - - Here are the 18 projects for this round of voting :) Please - carefully look at each of them. - - - {provided => ( -
- {projects.map((project, index) => ( - - {provided => ( -
{ - document - .getElementById('show-project') - .showModal() +
+ + Ships + + + Drag and drop the projects to vote for them. Click on the + projects to see the details. + + + {provided => ( +
+ {projects.map((project, index) => ( + + {provided => ( +
{ + provided.innerRef(el) + if (index === 0) dragItemRef.current = el // Capture ref for the first item as an example + }} + {...provided.draggableProps} + {...provided.dragHandleProps} + onClick={e => { + document + .getElementById('show-project') + .showModal() - setOpenProjectId(project.id) - }} - sx={{ - cursor: 'drag' - }} - > - -
- )} -
- ))} - {provided.placeholder} -
- )} -
-
-
-
- - Choose top 5 for{' '} - - - {showCreative - ? 'Most creative ships' - : showTechnical - ? 'Most technical ships' - : 'Best overall ships'} - - {['votes-1', 'votes-2', 'votes-3', 'votes-4', 'votes-5'].map( - voteId => ( + setOpenProjectId(project.id) + }} + sx={{ + ...provided.draggableProps.style + }} + // onMouseDown={handleMouseDown} + > + +
+ )} + + ))} + {provided.placeholder} +
+ )} + +
+
+
+ + Choose top 5 for{' '} + + + {showCreative + ? 'Most creative ships' + : showTechnical + ? 'Most technical ships' + : 'Best overall ships'} + + {[ + 'votes-1', + 'votes-2', + 'votes-3', + 'votes-4', + 'votes-5' + ].map(voteId => ( {provided => (
{
)}
- ) - )} - {isButtonActive ? ( - - ) : ( - - )} + ))} + {isButtonActive ? ( + + ) : ( + + )} +
-
-
+ - - {status == 'loading' && } - - {status == 'error' && } - - {status == 'success' && ( - - )} - { - document.getElementById('show-project').close() - }} - /> - -
+ className="gaegu" + > + {status == 'loading' && } - -
+ {status == 'error' && } + + {status == 'success' && ( + + )} + { + document.getElementById('show-project').close() + }} + /> + +
+ + +
+ ) ) + ) : startViewProject == true ? ( +
+
+ + {' '} + Ship {currentView + 1}/{projectCount} + +
+ + +
+
+
+ + + +
+ ) : ( +
+
+ + Voting for your favourite ships + + + When you click start voting, you will first be shown {projectCount}{' '} + projects in your cohort. After viewing these projects, you will get + the chance to pick your top 5 projects for each category. + + +
+ +
) } -export default My +export default Vote