Merge branch 'arcade-gallery' of https://github.com/claynicholson/site into pr/1312

This commit is contained in:
Max Wofford 2024-08-18 09:40:09 -04:00
commit e0bc7f2aa2
8 changed files with 408 additions and 134 deletions

View file

@ -19,7 +19,8 @@ const CohortCard = ({
githubLink = '',
draggable = false,
personal = false,
reload
reload,
color = ''
}) => {
const [isHovered, setIsHovered] = useState(false)

View file

@ -5,10 +5,20 @@ 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')
console.log('published changes', e)
console.log(color)
}
const { status, formProps, useField, data } = useForm(
`/api/arcade/showcase/projects/${project.id}/edit`,
@ -21,6 +31,38 @@ 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)
}
return (
<Box
sx={{
@ -28,7 +70,7 @@ const ProjectEditForm = ({ project }) => {
maxWidth: '1200px',
margin: 'auto',
position: 'relative',
my: 4
my: 5
}}
>
<Text
@ -40,15 +82,15 @@ const ProjectEditForm = ({ project }) => {
display: 'flex',
width: '100%',
mb: 2,
color: '#333',
color: '#333'
}}
>
<Icon glyph="edit" />
Editing {project.title} details
</Text>
<Text
as="a"
href="/arcade/showcase/my"
as="a"
href="/arcade/showcase/my"
sx={{
border: '2px dashed #333',
borderRadius: '5px',
@ -66,7 +108,7 @@ const ProjectEditForm = ({ project }) => {
mb: 3,
'&:hover': {
background: '#333',
color: '#F4E7C7'
color: '#f8e4c4'
}
}}
>
@ -90,6 +132,14 @@ const ProjectEditForm = ({ project }) => {
sx={{ border: '1px dashed', borderColor: '#09AFB4', mb: 2 }}
/>
</Label>
<Label>
<Text>Project description</Text>
<Input
{...useField('desc')}
placeholder="A summer of making for high schoolers"
sx={{ border: '1px dashed', borderColor: '#09AFB4', mb: 2 }}
/>
</Label>
<Label>
<Text>Repo Link</Text>
<Input
@ -106,7 +156,7 @@ const ProjectEditForm = ({ project }) => {
sx={{ border: '1px dashed', borderColor: '#09AFB4', mb: 2 }}
/>
</Label>
<Label>
{/* <Label>
<Text>Screenshot</Text>
<Input
{...useField('screenshot')}
@ -119,9 +169,175 @@ const ProjectEditForm = ({ project }) => {
{...useField('video')}
sx={{ border: '1px dashed', borderColor: '#09AFB4', mb: 2 }}
/>
</Label>
</Label> */}
<Input {...useField('authToken')} type="hidden" />
{/* <FileInput /> */}
<Label>
<Text>Add screenshots</Text>
<Flex sx={{ alignItems: 'center', mb: 2 }}>
<Input
type="url"
value={newScreenshot}
onChange={updateNewScreenshot}
sx={{ border: '1px dashed', borderColor: '#09AFB4' }}
/>
<Icon
onClick={() => updateScreenshot(newScreenshot)}
glyph="plus"
sx={{
transitionDuration: '0.3s',
cursor: 'pointer',
height: '37px',
width: '37px',
color: '#09AFB4',
ml: 2,
'&:hover': {
transform: 'scale(1.05)'
}
}}
/>
</Flex>
</Label>
{screenshot.map((image, index) => (
<div
key={index}
sx={{
display: 'grid',
position: 'relative',
mb: 3,
alignItems: 'center',
wordBreak: 'break-all',
gridTemplateColumns: '8fr 1fr'
}}
>
{image}
<Icon
onClick={() => deleteScreenshot(image)}
glyph="minus"
sx={{
transitionDuration: '0.3s',
height: '30px',
width: '30px',
color: '#DE4E2B',
cursor: 'pointer',
'&:hover': {
transform: 'scale(1.05)'
}
}}
/>
</div>
))}
<Input
{...useField('screenshot')}
value={JSON.stringify(screenshot)}
type="hidden"
/>
<Label>
<Text>Add videos</Text>
<Flex sx={{ alignItems: 'center', mb: 2 }}>
<Input
type="url"
value={newVideo}
onChange={updateNewVideo}
sx={{ border: '1px dashed', borderColor: '#09AFB4' }}
/>
<Icon
onClick={() => updateVideo(newVideo)}
glyph="plus"
sx={{
transitionDuration: '0.3s',
cursor: 'pointer',
height: '37px',
width: '37px',
color: '#09AFB4',
ml: 2,
'&:hover': {
transform: 'scale(1.05)'
}
}}
/>
</Flex>
</Label>
{video.map((image, index) => (
<div
key={index}
sx={{
display: 'grid',
position: 'relative',
mb: 3,
alignItems: 'center',
wordBreak: 'break-all',
gridTemplateColumns: '8fr 1fr'
}}
>
{image}
<Icon
onClick={() => deleteVideo(image)}
glyph="minus"
sx={{
transitionDuration: '0.3s',
height: '30px',
width: '30px',
color: '#DE4E2B',
cursor: 'pointer',
float: 'right',
'&:hover': {
transform: 'scale(1.05)'
}
}}
/>
</div>
))}
<Input
{...useField('video')}
value={JSON.stringify(video)}
type="hidden"
/>
<Label>
<Text>Background Color</Text>
<Input
{...useField('color')}
type="color"
// value={color}
// onChange={handleColorChange}
sx={{
width: '150px',
height: '50px',
padding: '0',
backgroundColor: 'transparent',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
zIndex: 1,
position: 'relative'
}}
/>
</Label>
<Label>
<Text>Text Color</Text>
<Input
{...useField('textColor')}
type="color"
sx={{
width: '150px',
height: '50px',
padding: '0',
backgroundColor: 'transparent',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
zIndex: 1,
position: 'relative'
}}
/>
</Label>
<Submit
status={status}
@ -135,10 +351,9 @@ const ProjectEditForm = ({ project }) => {
}}
/>
</form>
<FileInput />
<Box
sx={{
backgroundColor: '#FAEFD6',
// backgroundColor: color,
border: '2px dashed #09AFB4',
borderRadius: '5px'
}}

View file

@ -1,9 +1,38 @@
import styles from './project-view.module.css'
import { useState, useEffect } from 'react'
import randomNotFoundImg from './random-not-found-img'
import { Button, Text } from 'theme-ui'
import Icon from '@hackclub/icons'
/** @jsxImportSource theme-ui */
function darkenColor(hex, factor) {
let r = parseInt(hex.substring(1, 3), 16)
let g = parseInt(hex.substring(3, 5), 16)
let b = parseInt(hex.substring(5, 7), 16)
r = Math.floor(r * factor)
g = Math.floor(g * factor)
b = Math.floor(b * factor)
return (
'#' +
('0' + r.toString(16)).slice(-2) +
('0' + g.toString(16)).slice(-2) +
('0' + b.toString(16)).slice(-2)
)
}
function invertColor(hex) {
hex = hex.replace(/^#/, '')
let r = parseInt(hex.substring(0, 2), 16)
let g = parseInt(hex.substring(2, 4), 16)
let b = parseInt(hex.substring(4, 6), 16)
r = (255 - r).toString(16).padStart(2, '0')
g = (255 - g).toString(16).padStart(2, '0')
b = (255 - b).toString(16).padStart(2, '0')
return `#${r}${g}${b}`
}
const ProjectView = ({
id,
title = 'Title Not Found',
@ -15,45 +44,68 @@ const ProjectView = ({
githubProf,
user = 'User Not Found',
codeLink = '',
color = '',
textColor = '',
screenshot = [],
video = [],
...props
}) => {
const [darkColor, setDarkColor] = useState('#000000')
const [invertedColor, setInvertedColor] = useState('#000000')
const codeHost = codeLink.includes('github')
? 'View on GitHub'
: 'View project source'
const imagesList = images.length > 0 ? images : [randomNotFoundImg(id)]
useEffect(() => {
setDarkColor(darkenColor(color, 0.8))
setInvertedColor(invertColor(textColor))
}, [color])
return (
<div {...props} className="gaegu" sx={{ position: 'relative' }}>
<div sx={{ py: 4, backgroundColor: '#F4E7C7', textAlign: 'center', color: '#333' }}>
<h1 className="slackey" sx={{color: '#FF5C00'}}>{title}</h1>
<h3>By {user}</h3>
<Text
as="a"
href="/arcade/showcase/my"
<div
{...props}
className="gaegu"
sx={{ position: 'relative', backgroundColor: color }}
>
<div
sx={{
border: '2px dashed #333',
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,
color: '#333',
'&:hover': {
background: '#333',
color: '#F4E7C7'
}
py: 4,
backgroundColor: darkColor,
textAlign: 'center',
color: textColor
}}
>
<Icon glyph="home" /> View all my ships
</Text>
<h1 className="slackey">{title}</h1>
<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'
}
}}
>
<Icon glyph="home" /> View all my ships
</Text>
</div>
<div
@ -63,42 +115,67 @@ const ProjectView = ({
my: 3,
maxWidth: '800px',
display: 'grid',
gridTemplateColumns: images.length > 0 ? images.length > 1 ? '1fr' : ['1fr', '1fr 1fr'] : '',
gridTemplateColumns:
screenshot.length > 0
? videos.length > 0
? screenshot.length > 1
? '1fr'
: videos.length > 1
? '1fr'
: ['1fr', '1fr 1fr']
: ''
: '',
gap: '20px'
}}
>
<div
sx={{
display: 'grid',
flexWrap: 'wrap',
gridTemplateColumns: images > 1 ? ['1fr', '1fr 1fr', '1fr 1fr 1fr'] : '1fr'
}}
>
{imagesList.map((image, index) => (
<div
key={index}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<img
src={image}
alt={`Project image ${index + 1}`}
className={styles.image}
/>
</div>
))}
</div>
<div
sx={{
display: 'grid',
flexWrap: 'wrap',
gridTemplateColumns:
screenshot.length + videos.length > 1
? ['1fr', '1fr 1fr', '1fr 1fr 1fr']
: '1fr',
gap: '10px'
}}
>
{screenshot.map((image, index) => (
<div
key={index}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<img
src={image}
alt={`Project image ${index + 1}`}
className={styles.image}
/>
</div>
))}
{video.map((link, index) => (
<div key={index} style={{ marginBottom: '20px' }}>
<video sx={{ width: '100%', height: 'auto' }} controls>
<source src={link} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
))}
</div>
<p className={styles.description} sx={{textAlign: images.length != 1 ? 'center' : 'left'}}>{desc}</p>
<p
className={styles.description}
sx={{ textAlign: screenshot.length != 1 ? 'center' : 'left' }}
>
{desc}
</p>
</div>
<div
className={styles.buttonGroup}
sx={{ width: '90%', margin: 'auto', my: 3 }}
sx={{ width: '90%', margin: 'auto', pt: 1, pb: 5 }}
>
{playLink && (
<Button

View file

@ -1,5 +1,6 @@
import AirtablePlus from "airtable-plus";
import { ensureAuthed } from "../../login/test";
import { update } from "lodash";
export default async function handler(req, res) {
const user = await ensureAuthed(req)
@ -12,6 +13,7 @@ export default async function handler(req, res) {
return res.status(400).json({ error: "No body provided" })
}
const updatedFields = {}
updatedFields['Name'] = body.title
updatedFields['Description'] = body.desc
@ -19,6 +21,12 @@ export default async function handler(req, res) {
updatedFields['Code Link'] = body.codeLink
updatedFields['Play Link'] = body.playLink
updatedFields['Screenshot'] = body.images
updatedFields['color'] = body.color
updatedFields['textColor'] = body.textColor
updatedFields['ScreenshotLinks'] = body.screenshot
updatedFields['VideoLinks'] = body.video
console.log(body.color)
const airtable = new AirtablePlus({
apiKey: process.env.AIRTABLE_API_KEY,
@ -40,7 +48,12 @@ export default async function handler(req, res) {
playLink: project.fields['Play Link'] || '',
images: (project.fields['Screenshot'] || []).map(i => i.url),
user: user.fields['Name'],
githubProf: project.fields['Github Profile'] || ''
githubProf: project.fields['Github Profile'] || '',
color: project.fields['color'] || '',
textColor: project.fields['textColor'] || '',
screenshot: JSON.parse(p.fields['ScreenshotLinks']) || [],
video: JSON.parse(p.fields['VideoLinks']) || [],
}
return res.status(200).json({ project: results })

View file

@ -1,6 +1,6 @@
import AirtablePlus from "airtable-plus";
import { ensureAuthed } from "../../login/test";
import AirtablePlus from 'airtable-plus'
import { ensureAuthed } from '../../login/test'
import { closestTo } from 'date-fns'
export default async function handler(req, res) {
const user = await ensureAuthed(req)
@ -11,7 +11,7 @@ export default async function handler(req, res) {
const airtable = new AirtablePlus({
apiKey: process.env.AIRTABLE_API_KEY,
baseID: 'app4kCWulfB02bV8Q',
tableName: "Showcase"
tableName: 'Showcase'
})
const { projectID } = req.query
@ -31,7 +31,11 @@ export default async function handler(req, res) {
playLink: p.fields['Play Link'] || '',
images: (p.fields['Screenshot'] || []).map(i => i.url),
githubProf: p.fields['Github Profile'] || '',
user: user.fields['Name']
user: user.fields['Name'],
color: p.fields['color'] || '',
textColor: p.fields['textColor'] || '',
screenshot: JSON.parse(p.fields['ScreenshotLinks']) || [],
video: JSON.parse(p.fields['VideoLinks']) || []
}))
return res.status(200).json({ project: results[0] })
}
}

View file

@ -33,6 +33,7 @@ export default async function handler(req, res) {
"Name": name,
"Description": description,
"Play Link": playLink,
"color": "#FAEFD6"
})
return res.status(200).json({ project: project.id })

View file

@ -27,7 +27,8 @@ export default async function handler(req, res) {
playLink: p.fields['Play Link'] || '',
images: (p.fields['Screenshot'] || []).map(i => i.url),
githubProf: p.fields['Github Profile'] || '',
user: user.fields['Name']
user: user.fields['Name'],
color: p.fields['color'] || ''
}))
return res.status(200).json({ projects: results, name: user.fields['Name']})
}

View file

@ -44,23 +44,7 @@ const ProjectShowPage = ({ projectID }) => {
const [project, setProject] = useState([])
const [status, setStatus] = useState('loading')
const [errorMsg, setError] = useState(null)
// Spotlight effect
const spotlightRef = useRef()
useEffect(() => {
const handler = event => {
var rect = document.getElementById('spotlight').getBoundingClientRect()
var x = event.clientX - rect.left //x position within the element.
var y = event.clientY - rect.top //y position within the element.
spotlightRef.current.style.background = `radial-gradient(
circle at ${x}px ${y}px,
rgba(132, 146, 166, 0) 20px,
rgba(250, 239, 214, 0.9) 120px
)`
}
window.addEventListener('mousemove', handler)
return () => window.removeEventListener('mousemove', handler)
}, [])
useEffect(async () => {
const token = window.localStorage.getItem('arcade.authToken')
const response = await fetch(`/api/arcade/showcase/projects/${projectID}`, {
@ -84,33 +68,7 @@ const ProjectShowPage = ({ projectID }) => {
}, [])
return (
<Box
id="spotlight"
as="section"
sx={{
backgroundImage: `
linear-gradient(rgba(250, 239, 214, 0.7), rgba(250, 239, 214, 0.7)),
url('https://cloud-o19rieg4g-hack-club-bot.vercel.app/0group_495__1_.svg')
`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
position: 'relative',
minHeight: '100vh'
}}
>
<Box
ref={spotlightRef}
sx={{
position: 'absolute',
zIndex: 2,
top: 0,
left: 0,
right: 0,
bottom: 0,
bg: '#FAEFD6',
pointerEvents: 'none'
}}
/>
<div>
<div sx={{ zIndex: 5, position: 'relative' }}>
<img
src="https://cloud-677i45opw-hack-club-bot.vercel.app/0arcade_1.png"
@ -132,25 +90,29 @@ const ProjectShowPage = ({ projectID }) => {
status == 'error' && <ErrorMessage />
} */}
{/* {
{/* {
status == 'success' && */}
<ProjectView
key={project.id}
id={project.id}
title={project.title}
desc={project.desc}
slack={project.slackLink}
codeLink={project.codeLink}
playLink={project.playLink}
images={project.images}
githubProf={project.githubProf}
user={project.user}
/>
{/* } */}
<ProjectView
key={project.id}
id={project.id}
title={project.title}
desc={project.desc}
slack={project.slackLink}
codeLink={project.codeLink}
playLink={project.playLink}
images={project.images}
githubProf={project.githubProf}
user={project.user}
color={project.color}
textColor={project.textColor}
screenshot={project.screenshot}
video={project.video}
/>
{/* } */}
</div>
</div>
<style>{styled}</style>
</Box>
</div>
)
}