Merge branch 'main' into main

This commit is contained in:
Belle 2024-08-24 21:48:40 -04:00 committed by GitHub
commit f5059c2c1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 13590 additions and 158 deletions

4
.gitignore vendored
View file

@ -8,4 +8,6 @@ public/sitemap.xml
.vscode
yarn-error.log
bun.lockb
.idea
.idea
.yarn
.yarnrc.yml

View file

@ -71,16 +71,16 @@ const CohortCard = ({
<div
sx={{
position: 'absolute',
top: '10px',
left: '10px',
top: '16px',
left: '16px',
display: 'flex',
gap: '5px'
}}
>
<div
as="a"
<a
href={editLink}
sx={{
display: 'block',
color: 'white',
bg: '#09AFB4',
borderRadius: '10px',
@ -93,7 +93,7 @@ const CohortCard = ({
}}
>
<Icon glyph="edit" />{' '}
</div>
</a>
<div
onClick={e => {
modalRef.current?.showModal()
@ -120,11 +120,13 @@ const CohortCard = ({
target="_blank"
rel="noopener noreferrer"
>
<img
src={firstImage}
alt="Project Image"
className={styles.card_img}
/>
<div className={styles.card_img_container}>
<img
src={firstImage}
alt="Project Image"
className={styles.card_img}
/>
</div>
<h1 sx={{ color: textColor }} className={styles.card_title}>
{title}
</h1>

View file

@ -12,16 +12,23 @@
justify-content: flex-start;
position: relative;
padding: 10px;
border-radius: 10px;
}
.card_img {
width: 100%;
max-width: 200px;
max-width: 480px;
height: auto;
max-height: 100%;
object-fit: cover;
aspect-ratio: 1 / 1;
border-radius: 5px;
}
.card_img_container {
height: 200px;
align-items: center;
display: grid;
}
.card_title {

View file

@ -8,6 +8,12 @@
grid-gap: 20px;
padding: 10px;
margin: auto;
margin-bottom: 2rem;
/* quite wide screens */
@media (min-width: 481px) {
grid-template-columns: repeat(2, 1fr); /* 3 equal-width columns */
}
/* Small screens */
@media (min-width: 640px) {

View file

@ -118,7 +118,10 @@ const ProjectEditForm = ({ project }) => {
/>
</Label>
<Label>
<Text>Play Link</Text>
<Text>Project link</Text>
<Text variant="caption">
Direct link to your project. Binaries download page, Website link, etc.
</Text>
<Input
{...useField('playLink')}
placeholder="https://hackclub.com/arcade"
@ -206,6 +209,7 @@ const ProjectEditForm = ({ project }) => {
<Input
{...useField('hours')}
type="number"
min="1"
sx={{ border: '1px dashed', borderColor: '#09AFB4', mb: 2 }}
/>
</Label>

View file

@ -99,6 +99,7 @@ const ProjectView = ({
}, [readMeLink])
return (
// export a css property for each of the color and dark color
<div
{...props}
className="gaegu"
@ -110,6 +111,15 @@ const ProjectView = ({
width: '100%'
}}
>
<style>
{`
* {
--color: ${color};
--dark-color: ${darkColor};
--text-color: ${textColor};
}
`}
</style>
<div
sx={{
py: 4,
@ -139,7 +149,7 @@ const ProjectView = ({
target="_blank"
rel="noopener"
>
Play Now
Try Now!
</Button>
)}
@ -158,36 +168,37 @@ const ProjectView = ({
{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>
)}
<Text
as="a"
href="/arcade/showcase/my"
sx={{
border: `2px dashed ${textColor}`,
borderRadius: '5px',
position: ['relative', 'relative', 'absolute'],
display: 'flex',
minWidth: "fit-content",
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'
},
'@media screen and (max-width: 767px)': {
width: '50%',
left: 'calc(25%)'
}
}}
>
<Icon glyph="home" /> View all my ships
</Text>
</div>
<div

View file

@ -15,7 +15,7 @@
.image {
max-width: 24em;
max-height: 100%;
/* width: auto; */
width: 100%;
height: auto;
border-radius: 8px;
margin: 0 auto;

View file

@ -4,6 +4,9 @@ import rehypeRaw from 'rehype-raw'
import rehypeSanitize from 'rehype-sanitize'
import style from './readme-renderer.module.css'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { solarizedlight } from 'react-syntax-highlighter/dist/cjs/styles/prism'
const ReadmeRenderer = ({ markdown }) => {
return (
<ReactMarkdown
@ -11,9 +14,30 @@ const ReadmeRenderer = ({ markdown }) => {
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSanitize]}
linkTarget={'_blank'}
>
components={{
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
return !inline && match ? (
<SyntaxHighlighter
style={solarizedlight}
PreTag="div"
wrapLongLines={true}
language={match[1]}
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}}
>
{markdown}
</ReactMarkdown>
)
}
export default ReadmeRenderer
export default ReadmeRenderer

View file

@ -157,6 +157,10 @@ SOFTWARE.
margin-top: 0;
}
.reactMarkDown summary h1, .reactMarkDown summary h2, .reactMarkDown summary h3, .reactMarkDown summary h4, .reactMarkDown summary h5, .reactMarkDown summary h6 {
display: inline;
}
.reactMarkDown li p.first {
display: inline-block;
}
@ -226,6 +230,8 @@ SOFTWARE.
.reactMarkDown table {
padding: 0;
display: block;
overflow-x: auto;
}
.reactMarkDown table tr {
@ -354,42 +360,20 @@ SOFTWARE.
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 {
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}
.reactMarkDown pre div {
margin: 0 !important;
background-color: var(--color) !important;
filter: brightness(105%);
}
.reactMarkDown code, .reactMarkDown pre tt {
background-color: transparent;
border: none;
@ -399,4 +383,6 @@ SOFTWARE.
color: #e8912d;
font-size: 0.95rem;
border: 1px solid #CCCCCC !important;
margin: 0 2px;
padding: 0 5px;
}

View file

@ -79,6 +79,7 @@
"react-router-dom": "^6.22.3",
"react-scrolllock": "^5.0.1",
"react-snowfall": "^1.2.1",
"react-syntax-highlighter": "^15.5.0",
"react-ticker": "^1.3.2",
"react-tooltip": "^4.5.1",
"react-tsparticles": "^2.12.2",

View file

@ -1,21 +1,17 @@
import AirtablePlus from "airtable-plus"
import AirtablePlus from 'airtable-plus'
const airtable = new AirtablePlus({
apiKey: process.env.AIRTABLE_API_KEY,
baseID: 'app4kCWulfB02bV8Q',
tableName: "Users"
tableName: 'Users'
})
async function getUserFromLogin(loginToken) {
// only alphanumeric & '-' characters are allowed in the token
const safeLoginToken = loginToken.replace(/[^a-zA-Z0-9-]/g, '')
const results = await airtable.read({
filterByFormula: `{Login Token} = '${safeLoginToken}'`,
filterByFormula: `{Login Token} = '${loginToken}'`,
maxRecords: 1
})
return results[0]
}
@ -28,26 +24,34 @@ async function scrubLoginToken(userID) {
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: "Method not allowed" })
return res.status(405).json({ error: 'Method not allowed' })
}
const { token } = req.query
if (!token) {
return res.status(400).json({ error: "Token is required" })
return res.status(400).json({ error: 'Token is required' })
}
const user = await getUserFromLogin(token)
// only alphanumeric & '-' characters are allowed in the token
const safeLoginToken = token.replace(/[^a-zA-Z0-9-]/g, '')
if (safeLoginToken === '') {
return res
.status(400)
.json({ error: 'hey! no injection attacks thats mean' })
}
const user = await getUserFromLogin(safeLoginToken)
if (!user) {
return res.status(404).json({ error: "User not found" })
return res.status(404).json({ error: 'User not found' })
}
const authToken = user.fields['Auth Token']
if (!authToken) {
return res.status(500).json({ error: "Auth Token not found" })
return res.status(500).json({ error: 'Auth Token not found' })
}
await scrubLoginToken(user.id)
// return back the user's AuthToken
res.status(200).json({ authToken })
}
}

View file

@ -12,6 +12,25 @@ export default async function handler(req, res) {
return res.status(400).json({ error: 'No body provided' })
}
// html color input value always gives a 6-char hex color
const colorRegex = /^#[0-9A-F]{6}$/i;
if(body.color !== "" && !(colorRegex.test(body.color))) {
return res
.status(400)
.json({ error: 'Invalid Color' });
}
if(body.textColor !== "" && !(colorRegex.test(body.textColor))) {
return res
.status(400)
.json({ error: 'Invalid Text Color' });
}
if(body.hours <= 0) {
return res
.status(400)
.json({ error: 'Hours should be a positive integer' });
}
const updatedFields = {}
updatedFields['Name'] = body.title
updatedFields['Estimated Hours'] = body.hours

View file

@ -85,7 +85,7 @@ const ProjectShowPage = ({ projectID }) => {
key={project.id}
id={project.id}
title={project.title}
desc={project.desc}
description={project.description}
slack={project.slackLink}
codeLink={project.codeLink}
playLink={project.playLink}

13500
yarn.lock

File diff suppressed because it is too large Load diff