diff --git a/components/arcade/showcase/cohort-card.js b/components/arcade/showcase/cohort-card.js index c72df2d1..2fa5437d 100644 --- a/components/arcade/showcase/cohort-card.js +++ b/components/arcade/showcase/cohort-card.js @@ -54,7 +54,6 @@ const CohortCard = ({ } const firstImage = imageLink || randomNotFoundImg(id) - console.log({imageLink}) function red() { window.location.href = '/arcade/showcase/project/' + id + '/edit' @@ -125,7 +124,7 @@ const CohortCard = ({ } }} > - {' '} + {' '} )} @@ -170,7 +169,7 @@ const CohortCard = ({ }} onClick={e => { setIsVisible(false) - document.getElementById('add-project').close() + document.getElementById('delete-project').close() handleDelete() }} > @@ -186,7 +185,7 @@ const CohortCard = ({ color: '#09AFB4' }} onClick={e => { - document.getElementById('add-project').close() + document.getElementById('delete-project').close() }} /> diff --git a/components/arcade/showcase/project-edit.js b/components/arcade/showcase/project-edit.js index 3224fae1..fc697f50 100644 --- a/components/arcade/showcase/project-edit.js +++ b/components/arcade/showcase/project-edit.js @@ -1,26 +1,14 @@ -import { Input, Label, Text, Flex, Box, Grid } from 'theme-ui' +import { Input, Label, Text, Box, Grid } from 'theme-ui' import ProjectView from './project-view' import useForm from '../../../lib/use-form' import Submit from '../../submit' -import { useState } from 'react' import Icon from '@hackclub/icons' -// import FileInput from '../../../pages/api/arcade/showcase/projects/[projectID]/file-input' /** @jsxImportSource theme-ui */ const ProjectEditForm = ({ project }) => { - // const [previewProject, setPreviewProject] = useState(project) - const [screenshot, setScreenshot] = useState(project.screenshot) - const [newScreenshot, setNewScreenshot] = useState('') - - const [video, setVideo] = useState(project.video) - const [newVideo, setNewVideo] = useState('') - - function publishedChanges(e) { - console.log('published changes', e) - } const { status, formProps, useField, data } = useForm( `/api/arcade/showcase/projects/${project.id}/edit/`, - publishedChanges, + null, { method: 'PATCH', initData: { ...project, recordId: project.id }, @@ -29,38 +17,6 @@ const ProjectEditForm = ({ project }) => { } ) - const updateScreenshot = newMedia => { - if (screenshot.some(item => item === newMedia)) { - alert('This media already exists and cannot be added.') - return - } - setScreenshot(screenshot => [...screenshot, newMedia]) - } - - const deleteScreenshot = deletedMedia => { - setScreenshot(screenshot.filter(item => !item.includes(deletedMedia))) - } - - const updateNewScreenshot = e => { - setNewScreenshot(e.target.value) - } - - const updateVideo = newMedia => { - if (video.some(item => item === newMedia)) { - alert('This media already exists and cannot be added.') - return - } - setVideo(video => [...video, newMedia]) - } - - const deleteVideo = deletedMedia => { - setVideo(video.filter(item => !item.includes(deletedMedia))) - } - - const updateNewVideo = e => { - setNewVideo(e.target.value) - } - const previewProject = { ...data } @@ -134,6 +90,17 @@ const ProjectEditForm = ({ project }) => { sx={{ border: '1px dashed', borderColor: '#09AFB4', mb: 2 }} /> + + + + { { const [darkColor, setDarkColor] = useState('#000000') @@ -66,25 +68,24 @@ const ProjectView = ({ setInvertedColor(invertColor(textColor)) }, [color]) - function convertToRawUrl(githubUrl) { - if (!githubUrl.includes('github.com')) { - // throw new Error('Invalid GitHub URL') - return '' - } + // function convertToRawUrl(githubUrl) { + // if (!githubUrl.includes('github.com')) { + // // throw new Error('Invalid GitHub URL') + // return '' + // } - return githubUrl - .replace('github.com', 'raw.githubusercontent.com') - .replace('/blob/', '/') - } + // return githubUrl + // .replace('github.com', 'raw.githubusercontent.com') + // .replace('/blob/', '/') + // } const [markdown, setMarkdown] = useState('') useEffect(() => { const fetchMarkdown = async () => { - const rawReadMeLink = convertToRawUrl(readMeLink) - if (rawReadMeLink) { + if (readMeLink) { try { - const res = await fetch(rawReadMeLink) + const res = await fetch(readMeLink) const text = await res.text() setMarkdown(text) } catch (error) { @@ -101,7 +102,13 @@ const ProjectView = ({

{title}

+

{description}

By {user}

- - View all my ships - + {playLink && ( + + )} + + +
+ {preview ? ( + <> + ) : ( + + View all my ships + + )}
- { image != '' && ( + {image != '' && (
- Project Image + Project Image
)} - { video != '' && ( + + {/* { video != '' && (
- )} + )} */}

- -
- {playLink && ( - - )} - - -
) } diff --git a/components/arcade/showcase/readme-renderer.js b/components/arcade/showcase/readme-renderer.js index d314373e..03a1ccfe 100644 --- a/components/arcade/showcase/readme-renderer.js +++ b/components/arcade/showcase/readme-renderer.js @@ -1,14 +1,19 @@ -import ReactMarkdown from "react-markdown" -import remarkGfm from "remark-gfm" -import style from "./readme-renderer.module.css" +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import rehypeRaw from 'rehype-raw' +import rehypeSanitize from 'rehype-sanitize' +import style from './readme-renderer.module.css' const ReadmeRenderer = ({ markdown }) => { return ( + rehypePlugins={[rehypeRaw, rehypeSanitize]} + linkTarget={'_blank'} + > + {markdown} + ) } export default ReadmeRenderer \ No newline at end of file diff --git a/components/arcade/showcase/readme-renderer.module.css b/components/arcade/showcase/readme-renderer.module.css index d4d04d75..f61234e8 100644 --- a/components/arcade/showcase/readme-renderer.module.css +++ b/components/arcade/showcase/readme-renderer.module.css @@ -1,3 +1,396 @@ +.reactMarkDown { + text-align: left; +} .reactMarkDown img { max-width: 100%; -} \ No newline at end of file +} + +/* +Copyright (c) 2017 Chris Patuzzo +https://twitter.com/chrispatuzzo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +.reactMarkDown a { + color: #4183C4; + text-decoration: none; +} + +.reactMarkDown a.absent { + color: #cc0000; +} + +.reactMarkDown a.anchor { + display: block; + padding-left: 30px; + margin-left: -30px; + cursor: pointer; + position: absolute; + top: 0; + left: 0; + bottom: 0; +} + +.reactMarkDown h1, .reactMarkDown h2, .reactMarkDown h3, .reactMarkDown h4, .reactMarkDown h5, .reactMarkDown h6 { + margin: 20px 0 10px; + padding: 0; + font-weight: bold; + -webkit-font-smoothing: antialiased; + cursor: text; + position: relative; +} + +.reactMarkDown h2:first-child, .reactMarkDown h1:first-child, .reactMarkDown h1:first-child + h2, .reactMarkDown h3:first-child, .reactMarkDown h4:first-child, .reactMarkDown h5:first-child, .reactMarkDown h6:first-child { + margin-top: 0; + padding-top: 0; +} + +.reactMarkDown h1:hover a.anchor, .reactMarkDown h2:hover a.anchor, .reactMarkDown h3:hover a.anchor, .reactMarkDown h4:hover a.anchor, .reactMarkDown h5:hover a.anchor, .reactMarkDown h6:hover a.anchor { + text-decoration: none; +} + +.reactMarkDown h1 tt, .reactMarkDown h1 code { + font-size: inherit; +} + +.reactMarkDown h2 tt, .reactMarkDown h2 code { + font-size: inherit; +} + +.reactMarkDown h3 tt, .reactMarkDown h3 code { + font-size: inherit; +} + +.reactMarkDown h4 tt, .reactMarkDown h4 code { + font-size: inherit; +} + +.reactMarkDown h5 tt, .reactMarkDown h5 code { + font-size: inherit; +} + +.reactMarkDown h6 tt, .reactMarkDown h6 code { + font-size: inherit; +} + +.reactMarkDown h1 { + font-size: 28px; + /* color: black; */ +} + +.reactMarkDown h2 { + font-size: 24px; + border-bottom: 1px solid #cccccc; + /* color: black; */ +} + +.reactMarkDown h3 { + font-size: 18px; +} + +.reactMarkDown h4 { + font-size: 16px; +} + +.reactMarkDown h5 { + font-size: 14px; +} + +.reactMarkDown h6 { + font-size: 14px; +} + +.reactMarkDown p, .reactMarkDown blockquote, .reactMarkDown ul, .reactMarkDown ol, .reactMarkDown dl, .reactMarkDown li, .reactMarkDown table, .reactMarkDown pre { + margin: 15px 0; +} + +.reactMarkDown hr { + border: 0 none; + color: #cccccc; + height: 4px; + padding: 0; +} + +.reactMarkDown .reactMarkDown > h2:first-child { + margin-top: 0; + padding-top: 0; +} + +.reactMarkDown .reactMarkDown > h1:first-child { + margin-top: 0; + padding-top: 0; +} + +.reactMarkDown .reactMarkDown > h1:first-child + h2 { + margin-top: 0; + padding-top: 0; +} + +.reactMarkDown .reactMarkDown > h3:first-child, .reactMarkDown .reactMarkDown > h4:first-child, .reactMarkDown .reactMarkDown > h5:first-child, .reactMarkDown .reactMarkDown > h6:first-child { + margin-top: 0; + padding-top: 0; +} + +.reactMarkDown a:first-child h1, .reactMarkDown a:first-child h2, .reactMarkDown a:first-child h3, .reactMarkDown a:first-child h4, .reactMarkDown a:first-child h5, .reactMarkDown a:first-child h6 { + margin-top: 0; + padding-top: 0; +} + +.reactMarkDown h1 p, .reactMarkDown h2 p, .reactMarkDown h3 p, .reactMarkDown h4 p, .reactMarkDown h5 p, .reactMarkDown h6 p { + margin-top: 0; +} + +.reactMarkDown li p.first { + display: inline-block; +} + +.reactMarkDown ul, .reactMarkDown ol { + padding-left: 30px; +} + +.reactMarkDown ul :first-child, .reactMarkDown ol :first-child { + margin-top: 0; +} + +.reactMarkDown ul :last-child, .reactMarkDown ol :last-child { + margin-bottom: 0; +} + +.reactMarkDown dl { + padding: 0; +} + +.reactMarkDown dl dt { + font-size: 14px; + font-weight: bold; + font-style: italic; + padding: 0; + margin: 15px 0 5px; +} + +.reactMarkDown dl dt:first-child { + padding: 0; +} + +.reactMarkDown dl dt > :first-child { + margin-top: 0; +} + +.reactMarkDown dl dt > :last-child { + margin-bottom: 0; +} + +.reactMarkDown dl dd { + margin: 0 0 15px; + padding: 0 15px; +} + +.reactMarkDown dl dd > :first-child { + margin-top: 0; +} + +.reactMarkDown dl dd > :last-child { + margin-bottom: 0; +} + +.reactMarkDown blockquote { + border-left: 4px solid #dddddd; + padding: 0 15px; + color: #777777; +} + +.reactMarkDown blockquote > :first-child { + margin-top: 0; +} + +.reactMarkDown blockquote > :last-child { + margin-bottom: 0; +} + +.reactMarkDown table { + padding: 0; +} + +.reactMarkDown table tr { + border-top: 1px solid #cccccc; + background-color: white; + margin: 0; + padding: 0; +} + +.reactMarkDown table tr:nth-child(2n) { + background-color: #f8f8f8; +} + +.reactMarkDown table tr th { + font-weight: bold; + border: 1px solid #cccccc; + text-align: left; + margin: 0; + padding: 6px 13px; +} + +.reactMarkDown table tr td { + border: 1px solid #cccccc; + text-align: left; + margin: 0; + padding: 6px 13px; +} + +.reactMarkDown table tr th :first-child, .reactMarkDown table tr td :first-child { + margin-top: 0; +} + +.reactMarkDown table tr th :last-child, .reactMarkDown table tr td :last-child { + margin-bottom: 0; +} + +.reactMarkDown img { + max-width: 100%; +} + +.reactMarkDown span.frame { + display: block; + overflow: hidden; +} + +.reactMarkDown span.frame > span { + border: 1px solid #dddddd; + display: block; + float: left; + overflow: hidden; + margin: 13px 0 0; + padding: 7px; + width: auto; +} + +.reactMarkDown span.frame span img { + display: block; + float: left; +} + +.reactMarkDown span.frame span span { + clear: both; + color: #333333; + display: block; + padding: 5px 0 0; +} + +.reactMarkDown span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.reactMarkDown span.align-center > span { + display: block; + overflow: hidden; + margin: 13px auto 0; + text-align: center; +} + +.reactMarkDown span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.reactMarkDown span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.reactMarkDown span.align-right > span { + display: block; + overflow: hidden; + margin: 13px 0 0; + text-align: right; +} + +.reactMarkDown span.align-right span img { + margin: 0; + text-align: right; +} + +.reactMarkDown span.float-left { + display: block; + margin-right: 13px; + overflow: hidden; + float: left; +} + +.reactMarkDown span.float-left span { + margin: 13px 0 0; +} + +.reactMarkDown span.float-right { + display: block; + margin-left: 13px; + overflow: hidden; + float: right; +} + +.reactMarkDown span.float-right > span { + display: block; + overflow: hidden; + margin: 13px auto 0; + text-align: right; +} + +.reactMarkDown code, .reactMarkDown tt { + margin: 0 2px; + padding: 0 5px; + white-space: nowrap; + border: 1px solid #eaeaea; + background-color: #f8f8f8; + border-radius: 3px; +} + +.reactMarkDown pre code { + margin: 0; + padding: 0; + white-space: pre; + border: none; + background: transparent; +} + +.reactMarkDown .highlight pre { + background-color: #f8f8f8; + border: 1px solid #cccccc; + font-size: 13px; + line-height: 19px; + overflow: auto; + padding: 6px 10px; + border-radius: 3px; +} + +.reactMarkDown pre { + background-color: #f8f8f8; + border: 1px solid #cccccc; + font-size: 13px; + line-height: 19px; + overflow: auto; + padding: 6px 10px; + border-radius: 3px; +} + +.reactMarkDown code, .reactMarkDown pre tt { + background-color: transparent; + border: none; +} diff --git a/components/arcade/showcase/small-view-card.js b/components/arcade/showcase/small-view-card.js new file mode 100644 index 00000000..24876e32 --- /dev/null +++ b/components/arcade/showcase/small-view-card.js @@ -0,0 +1,33 @@ +import { useRef } from 'react' +import { Text, Close } from 'theme-ui' +import styles from './cohort-card.module.css' +import { useState } from 'react' +import { Button } from 'theme-ui' +import Icon from '@hackclub/icons' +import randomNotFoundImg from './random-not-found-img' +/** @jsxImportSource theme-ui */ + +const SmallView = ({ + id, + title = 'Title Not Found', + desc = 'Description Not Found' +}) => { + return ( +
+

{title}

+ +

{desc}

+
+ ) +} + +export default SmallView diff --git a/components/arcade/showcase/youtube-renderer.js b/components/arcade/showcase/youtube-renderer.js new file mode 100644 index 00000000..6b070e2b --- /dev/null +++ b/components/arcade/showcase/youtube-renderer.js @@ -0,0 +1,18 @@ +import LiteYouTubeEmbed from 'react-lite-youtube-embed'; +import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css' + +const YoutubeRenderer = ({ youtubeLink }) => { + if (!youtubeLink) return null + const youtubeID = youtubeLink.split('v=')[1] + if (!youtubeID) return

Invalid YouTube link: "{youtubeLink}"

+ + return ( + + ) +} + +export default YoutubeRenderer \ No newline at end of file diff --git a/package.json b/package.json index eb5ae56e..cf6f0425 100644 --- a/package.json +++ b/package.json @@ -62,12 +62,14 @@ "pcb-stackup": "^4.2.8", "rc-dialog": "^9.5.2", "react": "^17.0.2", + "react-beautiful-dnd": "^13.1.1", "react-before-after-slider-component": "^1.1.8", "react-countdown": "^2.3.6", "react-datepicker": "^4.24.0", "react-dom": "^17.0.2", "react-horizontal-scrolling-menu": "^6.0.2", "react-konami-code": "^2.3.0", + "react-lite-youtube-embed": "^2.4.0", "react-markdown": "^8", "react-marquee-slider": "^1.1.5", "react-masonry-css": "^1.0.16", @@ -84,6 +86,8 @@ "react-use-websocket": "^4.8.1", "react-wrap-balancer": "^1.1.0", "recharts": "2.12.2", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", "remark": "^15.0.1", "remark-gfm": "^3.0.1", "remark-html": "^16.0.1", diff --git a/pages/api/arcade/gallery.js b/pages/api/arcade/gallery.js deleted file mode 100644 index 356539ed..00000000 --- a/pages/api/arcade/gallery.js +++ /dev/null @@ -1,33 +0,0 @@ -import AirtablePlus from "airtable-plus" - -const fetchPosts = async () => { - const airtable = new AirtablePlus({ - apiKey: process.env.AIRTABLE_API_KEY, - baseID: 'app4kCWulfB02bV8Q', - tableName: 'Projects', - }) - - const records = await airtable.read({ - filterByFormula: '{Status} = "Shipped"' - }) - - return records.map(record => ({ - id: record.id, - name: record.fields["Name"], - desc: record.fields["Description"], - slack: record.fields["Slack Handle"], - codeLink: record.fields["Github Link"], - playLink: record.fields["Playable Link"], - images: record.fields["Screenshot / Video"], - })) -} - -export default async function handler(req, res) { - try { - const data = await fetchPosts(); - res.status(200).json(data); - } catch (error) { - console.error(error) - res.status(500).json({ error: 'Failed to fetch posts' }); - } -} \ No newline at end of file diff --git a/pages/api/arcade/showcase/projects/[projectID]/edit.js b/pages/api/arcade/showcase/projects/[projectID]/edit.js index 46bb73d2..de322882 100644 --- a/pages/api/arcade/showcase/projects/[projectID]/edit.js +++ b/pages/api/arcade/showcase/projects/[projectID]/edit.js @@ -14,11 +14,13 @@ export default async function handler(req, res) { const updatedFields = {} updatedFields['Name'] = body.title - updatedFields['Description'] = body.desc + updatedFields['Estimated Hours'] = body.hours + updatedFields['Description'] = body.description updatedFields['Slack Link'] = body.slackLink updatedFields['Code Link'] = body.codeLink updatedFields['Play Link'] = body.playLink updatedFields['Screenshot'] = [body.screenshot].map(i => ({ url: i })) + // updatedFields['Video'] = [body.video].map(i => ({ url: i })) updatedFields['color'] = body.color updatedFields['textColor'] = body.textColor updatedFields['ScreenshotLink'] = body.screenshot @@ -38,7 +40,8 @@ export default async function handler(req, res) { const results = { id: project.id, title: project.fields['Name'] || '', - desc: project.fields['Description'] || '', + hours: project.fields['Estimated Hours'] || '', + description: project.fields['Description'] || '', slackLink: project.fields['Slack Link'] || '', codeLink: project.fields['Code Link'] || '', slackLink: project.fields['Slack Link'] || '', diff --git a/pages/api/arcade/showcase/projects/[projectID]/index.js b/pages/api/arcade/showcase/projects/[projectID]/index.js index b2e31a19..00e512e2 100644 --- a/pages/api/arcade/showcase/projects/[projectID]/index.js +++ b/pages/api/arcade/showcase/projects/[projectID]/index.js @@ -42,7 +42,8 @@ export default async function handler(req, res) { const results = { id: p.id, title: p.fields['Name'] || '', - desc: p.fields['Description'] || '', + description: p.fields['Description'] || '', + hours: p.fields['Estimated Hours'], slackLink: p.fields['Slack Link'] || '', codeLink: p.fields['Code Link'] || '', slackLink: p.fields['Slack Link'] || '', @@ -53,7 +54,7 @@ export default async function handler(req, res) { color: p.fields['color'] || '', textColor: p.fields['textColor'] || '', screenshot: p.fields['ScreenshotLink'] || '', - video: p.fields['VideoLink'] || '', + video: p.fields['Video']?.[0]?.url || p.fields['VideoLink'] || '', readMeLink: p.fields['ReadMeLink'] || '' } return res.status(200).json({ project: results }) diff --git a/pages/api/arcade/showcase/vote.js b/pages/api/arcade/showcase/vote.js new file mode 100644 index 00000000..c7e2747b --- /dev/null +++ b/pages/api/arcade/showcase/vote.js @@ -0,0 +1,95 @@ +import AirtablePlus from 'airtable-plus'; + +const usersTable = new AirtablePlus({ + baseID: 'app4kCWulfB02bV8Q', + apiKey: process.env.AIRTABLE_API_KEY, + tableName: 'Users', +}); + +const votesTable = new AirtablePlus({ + baseID: 'app4kCWulfB02bV8Q', + apiKey: process.env.AIRTABLE_API_KEY, + tableName: 'Vote', +}); + +const showcaseTable = new AirtablePlus({ + baseID: 'app4kCWulfB02bV8Q', + apiKey: process.env.AIRTABLE_API_KEY, + tableName: 'Showcase', +}); + +export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + + const { authorization} = req.headers; + const { rank1, rank2, rank3 } = req.body; + return res.status(500).json({ error: req.body }); + + const pointsDistribution = [5, 4, 3, 2, 1]; + + for (let i = 0; i < rank1.length; i++) { + const project = rank1[i]; + const points = pointsDistribution[i]; + + addVote(project, points, authorization); + } + + for (let i = 0; i < rank2.length; i++) { + const project = rank2[i]; + const points = pointsDistribution[i]; + + addVote(project, points, authorization); + } + + for (let i = 0; i < rank3.length; i++) { + const project = rank3[i]; + const points = pointsDistribution[i]; + + addVote(project, points, authorization); + } + + { success: true, vote } + + res.status(200).json(); + } catch (error) { + console.error('Error creating vote:', error); + res.status(500).json({ error: 'Internal Server Error' }); + } +} + +const addVote = async (projectId, points, authorization) => { + if (!authorization || !points || !projectId) { + return res.status(400).json({ error: 'Missing required headers' }); + } + + const users = await usersTable.read({ + filterByFormula: `{Auth Token} = '${authorization}'` + }); + + if (users.length === 0) { + return res.status(404).json({ error: 'User not found' }); + } + + const user = users[0]; + console.log(user); + + const vote = await votesTable.create({ + Points: parseInt(points, 10), + Voter: [user.id] + }); + + const project = await showcaseTable.find(projectId); + + const updatedVotes = project.fields.Votes + ? [...project.fields.Votes, vote.id] + : [vote.id]; + + await showcaseTable.update(projectId, { + Votes: updatedVotes + }); + +} \ No newline at end of file diff --git a/pages/api/games.js b/pages/api/games.js index a7f067dc..7f0211f9 100644 --- a/pages/api/games.js +++ b/pages/api/games.js @@ -1,9 +1,15 @@ export async function getGames() { + try { + let games = await fetch( 'https://sprig.hackclub.com/api/gallery?new' ).then(res => res.json()) return games + } catch(e) { + console.error(e) + return [] + } } export default async function Games(req, res) { diff --git a/pages/api/github.js b/pages/api/github.js index 21d3e926..95aff5ef 100644 --- a/pages/api/github.js +++ b/pages/api/github.js @@ -32,6 +32,8 @@ const getUrl = (type, payload, repo) => { } export async function fetchGitHub() { + try { + const initialGitHubData = await fetch( 'https://api.github.com/orgs/hackclub/events' ).then(r => r.json()) @@ -48,6 +50,10 @@ export async function fetchGitHub() { })) return gitHubData + } catch (error) { + console.error(error) + return [] + } } export default async function github(req, res) { diff --git a/pages/api/sprig-console.js b/pages/api/sprig-console.js index 87da975b..1e404587 100644 --- a/pages/api/sprig-console.js +++ b/pages/api/sprig-console.js @@ -15,6 +15,11 @@ export async function getConsoles() { } export default async function SprigConsoles(req, res) { - const consoleCount = await getConsoles() + let consoleCount = 100 + try { + consoleCount = await getConsoles() + } catch (error) { + console.error(error) + } res.json(consoleCount) } diff --git a/pages/arcade/gallery.js b/pages/arcade/gallery.js deleted file mode 100644 index ac500282..00000000 --- a/pages/arcade/gallery.js +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react' -import Nav from '../../components/Nav' -import Footer from '../../components/arcade/Footer' -import BGImg from '../../components/background-image' -import background from '../../public/home/assemble.jpg' -import { Badge, Box, Button, Card, Container, Grid, Heading, Link, Text } from 'theme-ui' -import SlideDown from '../../components/slide-down' -import Post from '../../components/arcade/showcase/post' -import styles from '../../components/arcade/showcase/post.module.css' -import CohortCard from '../../components/arcade/showcase/cohort-card' - -export async function getStaticProps() { - const host = process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : 'https://hackclub.com'; - const res = await fetch(`${host}/api/arcade/gallery`); - const posts = await res.json(); - - const filteredPosts = posts; - - return { - props: { posts: filteredPosts, - }, - }; - } - - -const gallery = ({ posts }) => { - console.log(posts); - return ( -
-
- ) -} - -export default gallery \ No newline at end of file diff --git a/pages/arcade/showcase/index.js b/pages/arcade/showcase/index.js deleted file mode 100644 index d84ca506..00000000 --- a/pages/arcade/showcase/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' - -const index = () => { - return ( -
index
- ) -} - -export default index \ No newline at end of file diff --git a/pages/arcade/showcase/my.js b/pages/arcade/showcase/my.js index e038cc12..40b6c1b4 100644 --- a/pages/arcade/showcase/my.js +++ b/pages/arcade/showcase/my.js @@ -1,17 +1,10 @@ import { useEffect, useState, useRef } from 'react' import CohortCard from '../../../components/arcade/showcase/cohort-card' -import ProjectView from '../../../components/arcade/showcase/project-view' -import Nav from '../../../components/Nav' -import Footer from '../../../components/arcade/Footer' -import BGImg from '../../../components/background-image' -import background from '../../../public/arcade/homeBG.svg' import { Button, Heading, Text, Box, Close } from 'theme-ui' import SlideDown from '../../../components/slide-down' import styles from '../../../components/arcade/showcase/my.module.css' import Countdown from 'react-countdown' -import { StyleSheetContext } from 'styled-components' import Icon from '@hackclub/icons' -import Flag from '../../../components/flag' import ProjectAddView from '../../../components/arcade/showcase/project-add' /** @jsxImportSource theme-ui */ @@ -149,7 +142,7 @@ const My = () => { const [status, setStatus] = useState('loading') const [errorMsg, setError] = useState(null) - const launchDate = new Date(2024, 7, 19, 0, 0, 0, 0) + const launchDate = new Date(2025, 7, 25, 8, 0, 0, 0) const renderer = ({ hours, minutes, seconds, completed }) => { if (completed) { @@ -180,8 +173,9 @@ const My = () => { // Render a countdown return ( - First voting round in {hours > 0 ? `${hours} hours` : ''}{' '} - {minutes > 0 ? `${minutes} minutes` : ''} {seconds} seconds + First voting round in a week + {/* {hours > 0 ? `${hours} hours` : ''}{' '} + {minutes > 0 ? `${minutes} minutes` : ''} {seconds} seconds */} ) } diff --git a/pages/arcade/showcase/project/[projectID]/index.js b/pages/arcade/showcase/project/[projectID]/index.js index 5a076aec..fa242153 100644 --- a/pages/arcade/showcase/project/[projectID]/index.js +++ b/pages/arcade/showcase/project/[projectID]/index.js @@ -1,11 +1,6 @@ -import { useEffect, useState, useRef } from 'react' +import { useEffect, useState } from 'react' import ProjectView from '../../../../../components/arcade/showcase/project-view' -import Nav from '../../../../../components/Nav' -import Footer from '../../../../../components/arcade/Footer' -import BGImg from '../../../../../components/background-image' import styles from '../../../../../components/arcade/showcase/project-view.module.css' -import { Box, Text } from 'theme-ui' -import Icon from '@hackclub/icons' /** @jsxImportSource theme-ui */ const styled = ` @@ -26,7 +21,6 @@ body, html { body { background-color: #FAEFD6; - min-height: 100vh; } a { diff --git a/pages/arcade/showcase/test.js b/pages/arcade/showcase/test.js new file mode 100644 index 00000000..26e1097d --- /dev/null +++ b/pages/arcade/showcase/test.js @@ -0,0 +1,46 @@ +import React, { useEffect } from 'react'; + +const Test = () => { + + + const submitVote = async (rank1, rank2, rank3) => { + const authToken = window.localStorage.getItem('arcade.authToken'); + + try { + const response = await fetch('/api/arcade/showcase/vote', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': authToken, + }, + body: JSON.stringify({ + rank1, + rank2, + rank3, + }), + }) + + + const data = await response.json(); + return data; + } catch (error) { + console.error('Error submitting vote:', error); + throw error; + } + }; + + useEffect(() => { + + const rank1 = ["recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo"]; + const rank2 = ["recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo"]; + const rank3 = ["recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo", "recnSpjM91mCKJjlo"]; + + submitVote(rank1, rank2, rank3); + }, []); + + return ( +
test
+ ); +}; + +export default Test; diff --git a/pages/arcade/showcase/vote.js b/pages/arcade/showcase/vote.js new file mode 100644 index 00000000..df6e0415 --- /dev/null +++ b/pages/arcade/showcase/vote.js @@ -0,0 +1,770 @@ +import { useEffect, useState, useRef } from 'react' +import CohortCard from '../../../components/arcade/showcase/cohort-card' +import { Button, Heading, Text, Box, Close, Flex } from 'theme-ui' +import SlideDown from '../../../components/slide-down' +import styles from '../../../components/arcade/showcase/my.module.css' +import Countdown from 'react-countdown' +import Icon from '@hackclub/icons' +import ProjectView from '../../../components/arcade/showcase/project-view' +import SmallView from '../../../components/arcade/showcase/small-view-card' +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd' +import { over } from 'lodash' + +/** @jsxImportSource theme-ui */ + +const styled = ` +@import url('https://fonts.googleapis.com/css2?family=Slackey&family=Emblema+One&family=Gaegu&display=swap'); +body, html { + overflow-x: hidden; + } +.slackey { + font-family: "Slackey", sans-serif; + } + .emblema { + font-family: "Emblema One", system-ui; + } + + .gaegu { + font-family: "Gaegu", sans-serif; + } + + body { + background-color: #FAEFD6; + } + +@keyframes float { + + from, + to { + transform: translate(0%, -37%) rotate(-2deg); + } + + 25% { + transform: translate(-2%, -40%) rotate(2deg); + } + + 50% { + transform: translate(0%, -43%) rotate(-1deg); + } + + 75% { + transform: translate(-1%, -40%) rotate(-1deg); + } +} + +a { + color: inherit; +} +` + +const Loading = () => ( +
+ Loading... +
+) + +const ErrorMessage = () => ( +
+ There was an error loading your project. +
+) + +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' + // } + // } + // ] + // } + + // 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({}) + const [openProjectId, setOpenProjectId] = useState('') + const [openProject, setOpenProject] = useState([]) + const [showUIElements, setShowUIElements] = useState(false) + const [activeDroppableId, setActiveDroppableId] = useState(null) + const [activeDroppable, setActiveDroppable] = useState(true) + + const [submitStatus, setSubmitStatus] = useState('loading') + const [creative, setCreative] = useState([]) + const [technical, setTechnical] = useState([]) + const [overall, setOverall] = useState([]) + + 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 loadProjects = async () => { + const token = window.localStorage.getItem('arcade.authToken') + const response = await fetch('/api/arcade/showcase/cohort/active', { + method: 'GET', + headers: { + Authorization: `Bearer ${token}` + } + }).catch(e => { + console.error(e) + setLoadStatus('error') + setError(e) + }) + const data = await response.json() + if (data.error) { + setLoadStatus('error') + return + } else { + setProjects(data.showcases) + setOriginalProjects(data.showcases) + setLoadStatus('success') + } + } + + useEffect(async () => { + loadProjects() + }, []) + + const getProjectDetails = async () => { + const token = window.localStorage.getItem('arcade.authToken') + setStatus('loading') + + try { + const response = await fetch( + `/api/arcade/showcase/projects/${openProjectId}`, + { + method: 'GET', + headers: { + Authorization: `Bearer ${token}` + } + } + ) + + const data = await response.json() + + if (data.error) { + setStatus('error') + setError(data.error) + } else { + setOpenProject(data.project) + setStatus('success') + } + } catch (e) { + console.error(e) + setStatus('error') + setError(e.message) + } + } + + useEffect(() => { + getProjectDetails() + }, [openProjectId]) + + useEffect(() => { + setShowUIElements(true) + }, []) + + useEffect(() => { + console.log(Object.keys(votes).length) + 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 onDragUpdate = update => { + setActiveDroppable(true) + const { destination } = update + if (destination) { + setActiveDroppableId(destination.droppableId) + } + } + + const onDragEnd = result => { + const { source, destination } = result + + if (!destination) return + + if ( + source.droppableId === 'projects' && + destination.droppableId.startsWith('votes-') + ) { + const linkId = result.draggableId + const voteId = destination.droppableId + + setVotes(prevVotes => { + const updatedVotes = { + ...prevVotes, + [voteId]: linkId + } + + const votedProjectIds = Object.values(updatedVotes) + + const updatedProjects = originalProjects.filter( + project => !votedProjectIds.includes(project.id) + ) + + setProjects(updatedProjects) + + return updatedVotes + }) + } + setActiveDroppable(false) + } + + const deleteVote = voteId => { + setVotes(prevVotes => { + const updatedVotes = { ...prevVotes } + + delete updatedVotes[voteId] + + const votedProjectIds = Object.values(updatedVotes) + + const updatedProjects = originalProjects.filter( + project => !votedProjectIds.includes(project.id) + ) + + setProjects(updatedProjects) + + return updatedVotes + }) + } + + const renderVoteBox = voteId => { + const linkId = votes[voteId] + const project = originalProjects.find(link => link.id === linkId) + + if (project) { + return ( +
+ { + deleteVote(voteId) + }} + /> + + + {voteId.replace('votes-', '')} + + + + + {project.fields.Name} + + + {project.fields.Description} + + +
+ ) + } + + return ( +
+ + + {voteId.replace('votes-', '')} + + + + Drop here + +
+ ) + } + + const voteCreative = () => { + let ids = extractVotes(votes) + setCreative(ids) + setShowCreative(false) + setVotes({}) + setProjects(originalProjects) + setShowTechnical(true) + } + + const voteTechnical = () => { + let ids = extractVotes(votes) + setTechnical(ids) + setShowTechnical(false) + setVotes({}) + setProjects(originalProjects) + setShowOverall(true) + } + + const voteOverall = () => { + let ids = extractVotes(votes) + setOverall(ids) + setShowTechnical(false) + setVotes({}) + setProjects(originalProjects) + setEndPage(true) + } + + const submitVote = async (creative, technical, overall) => { + const authToken = window.localStorage.getItem('arcade.authToken') + + try { + const response = await fetch('/api/arcade/showcase/vote', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: authToken + }, + body: JSON.stringify({ + creative, + technical, + overall + }) + }) + + const data = await response.json() + setSubmitStatus('success') + return data + } catch (error) { + console.error('Error submitting vote:', error) + setSubmitStatus('error') + throw error + } + } + + useEffect(() => { + console.log('the votes') + console.log(creative) + console.log(technical) + console.log(overall) + + submitVote(creative, technical, overall) + }, [endPage]) + + return endPage == true ? ( +
+ {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() + + 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 => ( + + {provided => ( +
+ {renderVoteBox(voteId)} + {provided.placeholder} +
+ )} +
+ ) + )} + {isButtonActive ? ( + + ) : ( + + )} +
+
+
+
+ + + {status == 'loading' && } + + {status == 'error' && } + + {status == 'success' && ( + + )} + { + document.getElementById('show-project').close() + }} + /> + +
+ + +
+ ) + ) +} + +export default My diff --git a/pages/arcade/showcase/vote/project/[ID]/index.js b/pages/arcade/showcase/vote/project/[ID]/index.js deleted file mode 100644 index b8aac7d4..00000000 --- a/pages/arcade/showcase/vote/project/[ID]/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react' - -const Page = () => { - return ( -
Project
- ) -} - -export default Page \ No newline at end of file diff --git a/pages/bin/gallery.js b/pages/bin/gallery.js deleted file mode 100644 index f6995a1c..00000000 --- a/pages/bin/gallery.js +++ /dev/null @@ -1,131 +0,0 @@ -import React from 'react' -import BinPost from '../../components/bin/GalleryPosts' -import styles from '../../public/bin/style/gallery.module.css' -import Script from 'next/script' -import Nav from '../../components/bin/nav' -import Footer from '../../components/footer' -import PartTag from '../../components/bin/PartTag'; -import { useEffect, useRef, useState } from 'react'; -import { resolve } from 'styled-jsx/css'; -import { set } from 'lodash'; - -export async function getStaticProps() { - const host = process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : 'https://hackclub.com'; - const res = await fetch(`${host}/api/bin/gallery/posts/`); - const posts = await res.json(); - - const filteredPosts = posts.filter(post => post.status === 'Accepted' && post.parts && !post.hide); - - //Tags - - const resTag = await fetch(`${host}/api/bin/gallery/tags/`); - const tags = await resTag.json(); - - const filteredTags = tags.filter(tag => !tag.hide); - return { - props: { posts: filteredPosts, - tags: filteredTags - }, - }; -} - - - -function Gallery({ posts = [], tags = [] }) { - - const [allPosts, setAllPosts] = useState([]); - const [filterPosts, setFilterPosts] = useState([]); - const [filterParts, setFilterParts] = useState([]); - - useEffect(() => { - setAllPosts(posts); - setFilterParts([]); - - }, []); - - useEffect(() => { - setFilterPosts( - allPosts.filter(post => - post.parts && filterParts.every(part => post.parts.includes(part)) - ) - ); - }, [filterParts]); - - - const addFilter = (partID) => { - setFilterParts((prevParts) => { - if (!prevParts.includes(partID)) { - console.log("add", partID) - return [...prevParts, partID]; - } - return prevParts; - }); - - }; - - const removeFilter = (partID) => { - setFilterParts((prevParts) => { - return prevParts.filter(id => id !== partID); - }); - }; - - return ( -
- -
- - - - -
-
-
Want to add to the gallery? window.location.href = '/bin'}>Create a bin project in wokwi and your project will be added to the gallery!
-
- Search By Tag: -
- - {tags.map(tag => { - return ( - ) - - })} - -
- -
- - - - {filterPosts.map(post => { - return ( - ) - - })} - -
-
- ) -} - - - -export default Gallery diff --git a/pages/index.js b/pages/index.js index b1674a8a..cd34cdcd 100644 --- a/pages/index.js +++ b/pages/index.js @@ -1275,9 +1275,14 @@ export async function getStaticProps() { } hackathonsData.sort((a, b) => new Date(a.start) - new Date(b.start)) - let events = await fetch( - 'https://events.hackclub.com/api/events/upcoming/' - ).then(res => res.json()) + let events = [] + try { + await fetch( + 'https://events.hackclub.com/api/events/upcoming/' + ).then(res => res.json()) + } catch (error) { + console.error('Error fetching events:', error) + } return { props: { diff --git a/yarn.lock b/yarn.lock index 3620f3e1..3fc37530 100644 --- a/yarn.lock +++ b/yarn.lock @@ -992,7 +992,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== @@ -2130,6 +2130,14 @@ dependencies: "@types/unist" "*" +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -2208,11 +2216,29 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== -"@types/prop-types@^15.0.0": +"@types/prop-types@*", "@types/prop-types@^15.0.0": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== +"@types/react-redux@^7.1.20": + version "7.1.33" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.33.tgz#53c5564f03f1ded90904e3c90f77e4bd4dc20b15" + integrity sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react@*": + version "18.3.4" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.4.tgz#dfdd534a1d081307144c00e325c06e00312c93a3" + integrity sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/styled-system@^5.1.13": version "5.1.22" resolved "https://registry.yarnpkg.com/@types/styled-system/-/styled-system-5.1.22.tgz#508499f4c68bb86dde3454693e92f5771edf177f" @@ -3303,6 +3329,13 @@ crypto-browserify@3.12.0, crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +css-box-model@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" + integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== + dependencies: + tiny-invariant "^1.0.6" + css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" @@ -4961,7 +4994,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6040,6 +6073,11 @@ mdurl@^1.0.0: resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== +memoize-one@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -7363,6 +7401,11 @@ quick-lru@^1.0.0: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" integrity sha512-tRS7sTgyxMXtLum8L65daJnHUhfDUgboRdcWW2bR9vBfrj2+O5HSMbQOJfJJjIVSPFqbBCF37FpwWXGitDc5tA== +raf-schd@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" + integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -7416,6 +7459,19 @@ rc-util@^5.21.0, rc-util@^5.24.4, rc-util@^5.43.0: "@babel/runtime" "^7.18.3" react-is "^18.2.0" +react-beautiful-dnd@^13.1.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2" + integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ== + dependencies: + "@babel/runtime" "^7.9.2" + css-box-model "^1.2.0" + memoize-one "^5.1.1" + raf-schd "^4.0.2" + react-redux "^7.2.0" + redux "^4.0.4" + use-memo-one "^1.1.1" + react-before-after-slider-component@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/react-before-after-slider-component/-/react-before-after-slider-component-1.1.8.tgz#3df14381703aaa43428cdac05f271b8b492d25ca" @@ -7466,6 +7522,11 @@ react-is@16.13.1, react-is@^16.10.2, react-is@^16.13.1, react-is@^16.7.0, react- resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-is@^18.0.0, react-is@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" @@ -7478,6 +7539,11 @@ react-konami-code@^2.3.0: dependencies: prop-types "^15.8.1" +react-lite-youtube-embed@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/react-lite-youtube-embed/-/react-lite-youtube-embed-2.4.0.tgz#1f56a12be1061d50431444d52d836bd09a1283a2" + integrity sha512-Xo6cM1zPlROvvM97JkqQIoXstlQDaC4+DawmM7BB7Hh1cXrkBHEGq1iJlQxBTUWAUklmpcC7ph7qg7CztXtABQ== + react-markdown@^8: version "8.0.7" resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.7.tgz#c8dbd1b9ba5f1c5e7e5f2a44de465a3caafdf89b" @@ -7529,6 +7595,18 @@ react-popper@^2.3.0: react-fast-compare "^3.0.1" warning "^4.0.2" +react-redux@^7.2.0: + version "7.2.9" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" + integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/react-redux" "^7.1.20" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^17.0.2" + react-refresh@0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -7701,6 +7779,13 @@ recharts@2.12.2: tiny-invariant "^1.3.1" victory-vendor "^36.6.8" +redux@^4.0.0, redux@^4.0.4: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + reflect.getprototypeof@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" @@ -7803,6 +7888,15 @@ rehype-raw@^5.0.0: dependencies: hast-util-raw "^6.1.0" +rehype-raw@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4" + integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw "^9.0.0" + vfile "^6.0.0" + rehype-sanitize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/rehype-sanitize/-/rehype-sanitize-4.0.0.tgz#b5241cf66bcedc49cd4e924a5f7a252f00a151ad" @@ -7810,6 +7904,14 @@ rehype-sanitize@^4.0.0: dependencies: hast-util-sanitize "^3.0.0" +rehype-sanitize@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz#16e95f4a67a69cbf0f79e113c8e0df48203db73c" + integrity sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg== + dependencies: + "@types/hast" "^3.0.0" + hast-util-sanitize "^5.0.0" + rehype-stringify@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-8.0.0.tgz#9b6afb599bcf3165f10f93fc8548f9a03d2ec2ba" @@ -8372,16 +8474,7 @@ string-hash@1.1.3: resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8492,7 +8585,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -8506,13 +8599,6 @@ strip-ansi@6.0.0: dependencies: ansi-regex "^5.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -8692,7 +8778,7 @@ timers-browserify@2.0.12, timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -tiny-invariant@^1.3.1: +tiny-invariant@^1.0.6, tiny-invariant@^1.3.1: version "1.3.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== @@ -9192,6 +9278,11 @@ url@^0.11.0: punycode "^1.4.1" qs "^6.12.3" +use-memo-one@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" + integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== + use-subscription@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1"