This commit is contained in:
Clay Nicholson 2024-08-21 11:47:58 -04:00
commit 66ae91960f
25 changed files with 1661 additions and 481 deletions

View file

@ -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 = ({
}
}}
>
<Icon glyph="minus" />{' '}
<Icon glyph="delete" />{' '}
</div>
</div>
)}
@ -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()
}}
/>
</dialog>

View file

@ -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 }}
/>
</Label>
<Label>
<Text>Short description</Text>
<Text variant="caption">
This shows up on the showcase page. Keep it short and sweet!
</Text>
<Input
{...useField('description')}
placeholder="It's a party!"
sx={{ border: '1px dashed', borderColor: '#09AFB4', mb: 2 }}
/>
</Label>
<Label>
<Text>ReadMe Link</Text>
<Input
@ -160,11 +127,11 @@ const ProjectEditForm = ({ project }) => {
</Label>
<Label>
<Text>Screenshot link</Text>
<Text>Screenshot link (required)</Text>
<Text variant="caption">
Demo your work! No hosted link? Try{' '}
<a href="https://hackclub.slack.com/archives/C016DEDUL87">#cdn</a>{' '}
or <a href="https://tmpfiles.org/?upload">tmpfiles</a>
Demo your work! Post an image in{' '}
<a href="https://hackclub.slack.com/archives/C016DEDUL87" target="_blank">#cdn</a>{' '}
on Slack and paste the link here.
</Text>
<Input
{...useField('screenshot')}
@ -173,11 +140,9 @@ const ProjectEditForm = ({ project }) => {
/>
</Label>
<Label>
<Text>Video link</Text>
<Text>Video demo (optional)</Text>
<Text variant="caption">
Add a link to your demo video! Need a host? Try{' '}
<a href="https://hackclub.slack.com/archives/C016DEDUL87">#cdn</a>{' '}
or <a href="https://tmpfiles.org/?upload">tmpfiles</a>
Short video demoing your project. YouTube link. Suggested for hardware projects.
</Text>
<Input
{...useField('video')}
@ -191,8 +156,6 @@ const ProjectEditForm = ({ project }) => {
<Input
{...useField('color')}
type="color"
// value={color}
// onChange={handleColorChange}
sx={{
width: '150px',
height: '50px',
@ -224,6 +187,29 @@ const ProjectEditForm = ({ project }) => {
}}
/>
</Label>
<Label>
<Text>#scrapbook Slack Link</Text>
<Text variant="caption">
This is just to show you worked on this for arcade!
</Text>
<Input
{...useField('slackLink')}
placeholder="https://hackclub.slack.com/archives/C016DEDUL87"
sx={{ border: '1px dashed', borderColor: '#09AFB4', mb: 2 }}
/>
</Label>
<Label>
<Text>Hours (estimated)</Text>
<Text variant="caption">
This isn't shown on the site and won't affect your chances, but it'll help us guage how accurate arcade was. Please be honest this is just feedback for us for future events we run!
</Text>
<Input
{...useField('hours')}
type="number"
sx={{ border: '1px dashed', borderColor: '#09AFB4', mb: 2 }}
/>
</Label>
<Input {...useField('authToken')} type="hidden" />
<Submit
@ -240,7 +226,6 @@ const ProjectEditForm = ({ project }) => {
</form>
<Box
sx={{
// backgroundColor: color,
border: '2px dashed #09AFB4',
borderRadius: '5px'
}}

View file

@ -4,6 +4,7 @@ import randomNotFoundImg from './random-not-found-img'
import { Button, Text } from 'theme-ui'
import Icon from '@hackclub/icons'
import ReadmeRenderer from './readme-renderer'
import YoutubeRenderer from './youtube-renderer'
/** @jsxImportSource theme-ui */
function darkenColor(hex, factor) {
@ -37,7 +38,7 @@ function invertColor(hex) {
const ProjectView = ({
id,
title = 'Title Not Found',
desc = 'Description Not Found',
description = 'Description Not Found',
slack = 'Slack Not Found',
scrapbook = '',
playLink,
@ -50,6 +51,7 @@ const ProjectView = ({
screenshot = '',
video = '',
readMeLink = '',
preview,
...props
}) => {
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 = ({
<div
{...props}
className="gaegu"
sx={{ position: 'relative', backgroundColor: color, color: textColor }}
sx={{
position: 'relative',
backgroundColor: color,
color: textColor,
minHeight: '100vh',
width: '100%'
}}
>
<div
sx={{
@ -112,33 +119,75 @@ const ProjectView = ({
}}
>
<h1 className="slackey">{title}</h1>
<h2>{description}</h2>
<h3>By {user}</h3>
<Text
as="a"
href="/arcade/showcase/my"
sx={{
border: `2px dashed ${textColor}`,
borderRadius: '5px',
position: ['relative', 'relative', 'absolute'],
display: 'flex',
left: '10px',
top: '10px',
justifyContent: 'center',
alignItems: 'center',
px: 2,
py: 1,
transitionDuration: '0.4s',
cursor: 'pointer',
textDecoration: 'none',
mb: 3,
'&:hover': {
background: textColor || '#333',
color: invertedColor || '#F4E7C7'
}
}}
<div
className={styles.buttonGroup}
sx={{ width: '90%', margin: 'auto', pt: 2, pb: 2 }}
>
<Icon glyph="home" /> View all my ships
</Text>
{playLink && (
<Button
as="a"
sx={{
backgroundColor: '#FF5C00',
color: '#ebebeb',
textSizeAdjust: '16px',
borderRadius: '10px'
}}
href={playLink}
target="_blank"
rel="noopener"
>
Play Now
</Button>
)}
<Button
as="a"
sx={{
backgroundColor: '#09AFB4',
color: '#ebebeb',
textSizeAdjust: '16px',
borderRadius: '10px'
}}
href={codeLink}
target="_blank"
rel="noopener"
>
{codeHost}
</Button>
</div>
{preview ? (
<></>
) : (
<Text
as="a"
href="/arcade/showcase/my"
sx={{
border: `2px dashed ${textColor}`,
borderRadius: '5px',
position: ['relative', 'relative', 'absolute'],
display: 'flex',
left: '10px',
top: '10px',
justifyContent: 'center',
alignItems: 'center',
px: 2,
py: 1,
transitionDuration: '0.4s',
cursor: 'pointer',
textDecoration: 'none',
mb: 3,
'&:hover': {
background: textColor || '#333',
color: invertedColor || '#F4E7C7'
}
}}
>
<Icon glyph="home" /> View all my ships
</Text>
)}
</div>
<div
@ -146,7 +195,7 @@ const ProjectView = ({
width: '90%',
margin: 'auto',
my: 3,
maxWidth: '800px',
maxWidth: '800px'
}}
>
<div
@ -160,7 +209,7 @@ const ProjectView = ({
gap: '10px'
}}
>
{ image != '' && (
{image != '' && (
<div
sx={{
display: 'flex',
@ -168,14 +217,11 @@ const ProjectView = ({
justifyContent: 'center'
}}
>
<img
src={image}
alt="Project Image"
className={styles.image}
/>
<img src={image} alt="Project Image" className={styles.image} />
</div>
)}
{ video != '' && (
<YoutubeRenderer youtubeLink={video} />
{/* { video != '' && (
<div
sx={{
display: 'flex',
@ -188,7 +234,7 @@ const ProjectView = ({
Your browser does not support the video tag.
</video>
</div>
)}
)} */}
</div>
<p
@ -198,43 +244,6 @@ const ProjectView = ({
<ReadmeRenderer markdown={markdown} />
</p>
</div>
<div
className={styles.buttonGroup}
sx={{ width: '90%', margin: 'auto', pt: 1, pb: 5 }}
>
{playLink && (
<Button
as="a"
sx={{
backgroundColor: '#FF5C00',
color: '#ebebeb',
textSizeAdjust: '16px',
borderRadius: '10px'
}}
href={playLink}
target="_blank"
rel="noopener"
>
Play Now
</Button>
)}
<Button
as="a"
sx={{
backgroundColor: '#09AFB4',
color: '#ebebeb',
textSizeAdjust: '16px',
borderRadius: '10px'
}}
href={codeLink}
target="_blank"
rel="noopener"
>
{codeHost}
</Button>
</div>
</div>
)
}

View file

@ -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 (
<ReactMarkdown
className={style.reactMarkDown}
remarkPlugins={[remarkGfm]}
children={markdown}
/>
rehypePlugins={[rehypeRaw, rehypeSanitize]}
linkTarget={'_blank'}
>
{markdown}
</ReactMarkdown>
)
}
export default ReadmeRenderer

View file

@ -1,3 +1,396 @@
.reactMarkDown {
text-align: left;
}
.reactMarkDown img {
max-width: 100%;
}
}
/*
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;
}

View file

@ -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 (
<div
sx={{
border: '2px dashed #09AFB4',
borderRadius: '10px',
px: 3,
color: '#35290F',
height: '100%'
}}
id={id}
>
<h1 sx={{ mt: 2, mb: 0, wordBreak: 'break-all' }}>{title}</h1>
<p sx={{ mt: 0 }}>{desc}</p>
</div>
)
}
export default SmallView

View file

@ -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 <p>Invalid YouTube link: "{youtubeLink}"</p>
return (
<LiteYouTubeEmbed
id={youtubeID}
adNetwork={false}
noCookie={true}
/>
)
}
export default YoutubeRenderer

View file

@ -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",

View file

@ -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' });
}
}

View file

@ -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'] || '',

View file

@ -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 })

View file

@ -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
});
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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)
}

View file

@ -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 (
<section>
<Nav />
<BGImg
src={background}
alt="Arcade Gallery BG Img"
priority
/>
<SlideDown duration={768}>
<Heading
as="h1"
variant="ultratitle"
sx={{
color: 'white',
textShadow: 'text',
filter: 'drop-shadow(0 -2px 4px rgba(0,0,0,0.5))',
WebkitFilter: 'drop-shadow(0 -2px 4px rgba(0,0,0,0.5))',
maxWidth: [null, 'copyUltra'],
my: [3, 4],
mx: 'auto',
zIndex: 1
}}
>
<Text
as="span"
sx={{
WebkitTextStroke: 'currentColor',
WebkitTextStrokeWidth: ['2px', '3px'],
WebkitTextFillColor: 'transparent'
}}
>
Arcade Gallery
</Text>
<br />
<Button
as="a"
variant="ctaLg"
href="https://apply.hackclub.com"
target="_blank"
rel="noopener"
>
Add a Project
</Button>
</Heading>
</SlideDown>
<div className={styles.feed}>
<CohortCard/>
{posts.map(post => {
return (
<Post
id={post.id}
title={post.name}
desc={post.desc}
slack={post.slack}
codeLink={post.codeLink}
playable={post.playable}
images={post.images}
githubProf={""}
key={post.id}
/>)
})}
</div>
<Footer />
</section>
)
}
export default gallery

View file

@ -1,9 +0,0 @@
import React from 'react'
const index = () => {
return (
<div>index</div>
)
}
export default index

View file

@ -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 (
<span sx={{ color: '#FF5C00' }}>
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 */}
</span>
)
}

View file

@ -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 {

View file

@ -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 (
<div>test</div>
);
};
export default Test;

View file

@ -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 = () => (
<div
sx={{
width: '100%',
maxWidth: '1200px',
margin: 'auto',
textAlign: 'center'
}}
>
Loading...
</div>
)
const ErrorMessage = () => (
<div
sx={{
width: '100%',
maxWidth: '1200px',
margin: 'auto',
textAlign: 'center'
}}
>
There was an error loading your project.
</div>
)
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 (
<div
sx={{
backgroundColor: '#F6E7C5',
borderRadius: '10px',
position: 'relative',
height: '100%',
display: 'flex',
alignItems: 'center'
}}
>
<Close
sx={{
'&:hover': { cursor: 'pointer' },
position: 'absolute',
top: '0px',
right: '0px',
zIndex: 2,
color: '#09AFB4'
}}
onClick={e => {
deleteVote(voteId)
}}
/>
<Flex
sx={{
background: '#09AFB4',
px: 2,
py: 1,
color: '#FAEFD6',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
width: '50px',
flex: '0 0 50px'
}}
>
<Text variant="subtitle" sx={{ mt: 0 }}>
{voteId.replace('votes-', '')}
</Text>
</Flex>
<Box sx={{ pl: 3 }}>
<Text variant="subtitle" as="h3" sx={{ mt: 0, fontWeight: 'bold' }}>
{project.fields.Name}
</Text>
<Text variant="caption" as="p" sx={{ color: '#35290F' }}>
{project.fields.Description}
</Text>
</Box>
</div>
)
}
return (
<div
sx={{
display: 'flex',
alignItems: 'center',
// justifyContent: 'center',
height: '100%',
position: 'relative',
backgroundColor: '#FAEFD6',
borderRadius: '10px'
}}
>
<Flex
sx={{
background: '#09AFB4',
px: 2,
py: 1,
color: '#FAEFD6',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
width: '50px',
flex: '0 0 50px'
}}
>
<Text variant="subtitle" sx={{ mt: 0 }}>
{voteId.replace('votes-', '')}
</Text>
</Flex>
<Text sx={{ display: 'block', textAlign: 'center', width: '100%' }}>
Drop here
</Text>
</div>
)
}
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 ? (
<div
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
width: '100vw',
backgroundColor: '#FAEFD6'
}}
>
{submitStatus == 'loading'
? 'Loading'
: submitStatus == 'success'
? 'Thanks for voting!'
: 'Ran into an error sending your votes'}
</div>
) : (
showUIElements && (
<div>
<div
sx={{
zIndex: 5,
position: 'relative',
color: '#35290F',
height: '100vh'
}}
className="gaegu"
>
<DragDropContext onDragEnd={onDragEnd} onDragUpdate={onDragUpdate}>
<div
sx={{
display: 'grid',
gridTemplateColumns: ['1fr', '1fr', '1fr 1fr'],
height: '100%',
minHeight: '100vh'
}}
>
<div
sx={{
backgroundColor: '#F6E7C5',
pr: 3,
py: 5,
pl: 4,
overflowY: 'scroll',
height: '100vh'
}}
>
<Text variant="title" className="slackey" as="h1">
Ships
</Text>
<Text variant="subtitle" as="h4" sx={{ mb: 3 }}>
Here are the 18 projects for this round of voting :) Please
carefully look at each of them.
</Text>
<Droppable droppableId="projects">
{provided => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
sx={{
width: '100%',
display: 'grid',
gridTemplateColumns: ['1fr', '1fr', '1fr 1fr'],
gap: '10px'
}}
>
{projects.map((project, index) => (
<Draggable
key={project.id}
draggableId={project.id}
index={index}
>
{provided => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
onClick={e => {
document
.getElementById('show-project')
.showModal()
setOpenProjectId(project.id)
}}
sx={{
cursor: 'drag'
}}
>
<SmallView
id={project.id}
title={project.fields.Name}
desc={project.fields.Description}
/>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
<div sx={{ pl: 3, py: 5, pr: 4 }}>
<div>
<Text as="p" variant="subtitle">
Choose top 5 for{' '}
</Text>
<Text
as="p"
className="slackey"
variant="subtitle"
sx={{ pb: 3 }}
>
{showCreative
? 'Most creative ships'
: showTechnical
? 'Most technical ships'
: 'Best overall ships'}
</Text>
{['votes-1', 'votes-2', 'votes-3', 'votes-4', 'votes-5'].map(
voteId => (
<Droppable key={voteId} droppableId={voteId}>
{provided => (
<div
ref={provided.innerRef}
{...provided.droppableProps}
sx={{
width: '100%',
height: ['14vh', '14vh', '12vh'],
minHeight: '80px',
border: '2px dashed #09AFB4',
borderRadius: '10px',
mb: 3,
backgroundColor:
activeDroppableId === voteId
? '#F6E7C5'
: '#FAEFD6',
transition: 'transform 0.2s ease',
transform:
activeDroppableId === voteId
? activeDroppable == true
? 'scale(1.05)'
: 'scale(1)'
: 'scale(1)'
}}
>
{renderVoteBox(voteId)}
{provided.placeholder}
</div>
)}
</Droppable>
)
)}
{isButtonActive ? (
<Button
onClick={e => {
showCreative
? voteCreative()
: showTechnical
? voteTechnical()
: showOverall
? voteOverall()
: null
}}
sx={{
backgroundColor: '#09AFB4',
color: '#FAEFD6',
borderRadius: '5px',
border: 'none',
px: '20px',
transitionDuration: '0.3s',
'&:hover': {
transform: 'scale(1.05)'
},
width: 'fit-content'
}}
>
Place votes
</Button>
) : (
<Button
sx={{
backgroundColor: '#09AFB4',
color: '#FAEFD6',
borderRadius: '5px',
border: 'none',
px: '20px',
width: 'fit-content',
opacity: 0.4,
transition: 'none',
'&:hover': {
transform: 'none'
},
cursor: 'not-allowed'
}}
>
Place votes
</Button>
)}
</div>
</div>
</div>
</DragDropContext>
<dialog
id="show-project"
sx={{
borderRadius: '10px',
border: '3px dashed #09AFB4',
width: '70vw'
}}
className="gaegu"
>
{status == 'loading' && <Loading />}
{status == 'error' && <ErrorMessage />}
{status == 'success' && (
<ProjectView
preview="preview"
key={openProject.id}
id={openProject.id}
title={openProject.title}
desc={openProject.desc}
slack={openProject.slackLink}
codeLink={openProject.codeLink}
playLink={openProject.playLink}
images={openProject.images}
githubProf={openProject.githubProf}
user={openProject.user}
color={openProject.color}
textColor={openProject.textColor}
screenshot={openProject.screenshot}
video={openProject.video}
readMeLink={openProject.readMeLink}
/>
)}
<Close
sx={{
'&:hover': { cursor: 'pointer' },
position: 'absolute',
top: '10px',
right: '10px',
zIndex: 2,
color: '#09AFB4'
}}
onClick={e => {
document.getElementById('show-project').close()
}}
/>
</dialog>
</div>
<style>{styled}</style>
</div>
)
)
}
export default My

View file

@ -1,9 +0,0 @@
import React from 'react'
const Page = () => {
return (
<div>Project</div>
)
}
export default Page

View file

@ -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 (
<section className='page'>
<div className={styles.background}></div>
<Script src="https://awdev.codes/utils/hackclub/orph.js"></Script>
<div className={styles.header_div}>
<Nav />
<h1 className={styles.title}>Bin Gallery</h1>
<p className={styles.sub_title}>A display of all of bin's projects</p>
</div>
<div className={styles.text_container}> <span className={styles.first}>Want to add to the gallery? </span><span className={styles.second} onClick={() => window.location.href = '/bin'}>Create a bin project in wokwi </span><span className={styles.third}>and your project will be added to the gallery!</span><br/>
</div>
<span className={styles.tag_text}>Search By Tag:</span>
<div className={styles.tag_search_container}>
{tags.map(tag => {
return (
<PartTag
partID={tag.ID}
key={tag.ID}
search={true}
addFilter={addFilter}
removeFilter={removeFilter}
/>)
})}
</div>
<div className={styles.feed}>
{filterPosts.map(post => {
return (
<BinPost
key={post.ID}
id={post.ID}
title={post.title}
desc={post.desc}
slack={post.slack}
link={post.link}
date={post.created}
parts={post.parts}
/>)
})}
</div>
<Footer />
</section>
)
}
export default Gallery

View file

@ -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: {

135
yarn.lock
View file

@ -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"