Add initial version of signed-out arcade shop

This commit is contained in:
SkyfallWasTaken 2024-06-22 10:10:21 +01:00
parent e3046ff4b4
commit ed3bac8d49
4 changed files with 376 additions and 19 deletions

View file

@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect } from 'react'
import { Box, Button, Text, Flex, Grid, Card, Link, Close, Divider } from 'theme-ui'
import React from 'react'
import { Button, Text, Flex, Close, Divider } from 'theme-ui'
import Balancer from 'react-wrap-balancer'
import Quantity from './quantity'
/** @jsxImportSource theme-ui */
@ -14,11 +14,11 @@ const Prizes = ({
cost,
polaroidRotation,
ticketRotation,
link,
link = null,
quantity,
onQuantityChange,
index,
hoursBalance,
hoursBalance = null,
...props
}) => {
const parsedFulfillmentDesc = fulfillmentDescription?.replace(
@ -96,11 +96,11 @@ const Prizes = ({
</Balancer>
<Flex>
{// only show the quantity dropdown if you have enough hours to buy at least 2 of the item
hoursBalance / cost < 2 ? (null) : <Quantity numOptions={Math.min(quantity, Math.floor(hoursBalance / cost))} label={text} onQuantityChange={onQuantityChange} index={index} />
(hoursBalance ? hoursBalance / cost < 2 : (null)) ? (null) : <Quantity numOptions={Math.min(quantity, Math.floor(hoursBalance / cost))} label={text} onQuantityChange={onQuantityChange} index={index} />
}
{
// only show the buy button if you have enough hours to buy at least 1 of the item
hoursBalance / cost < 1 ? (null) :
(hoursBalance ? hoursBalance / cost < 1 : (null)) ? (null) :
<Button
sx={{
borderRadius: '5px',

View file

@ -40,7 +40,7 @@ export default function ShopComponent({
return `https://forms.hackclub.com/arcade-order?user_id=${userAirtableID}&item_id=${itemID}&quantity=${quantity}`;
}
const includeBuyLink = userAirtableID !== null;
const canPurchaseItems = userAirtableID !== null;
useEffect(() => {
setPRotate(2 + Math.random() * 4) * (Math.random() > 0.5 ? 1 : -1)
setTRotate(5 + Math.random() * 14) * (Math.random() > 0.5 ? 1 : -1)
@ -89,11 +89,10 @@ export default function ShopComponent({
fullName={item['Full Name']}
polaroidRotation={pRotate}
ticketRotation={tRotate}
link={buyLink(item.id)}
link={canPurchaseItems ? buyLink(item.id) : null}
key={item.id}
id={item.id}
onQuantityChange={(id, q) => handleQuantityChange(item.id, q)} // Pass handler to update quantity
hoursBalance={hoursBalance}
/>
))}
</Grid>

View file

@ -1,20 +1,289 @@
import AirtablePlus from "airtable-plus"
export const shopParts = async () => {
const airtable = new AirtablePlus({
apiKey: process.env.AIRTABLE_API_KEY,
baseID: "app4kCWulfB02bV8Q",
tableName: "Shop Items"
})
const records = await airtable.read()
return records.map(record => ({id: record.id, ...record.fields}))
}
return [
{
"Name": "Pile of stickers",
"Small Name": null,
"Full Name": "Pile of stickers ",
"Description": "Dealer's choice! We'll send you 3 random stickers. You can have whatever you want, as long as it's random!",
"Fulfillment Description": "Available anywhere\n",
"Cost Hours": 1,
"id": "rec7rzTHzjLa1ZysL",
"Image URL": "https://cloud-c1gqq7ttf-hack-club-bot.vercel.app/0sticker_pile_2.png",
"Max Order Quantity": 10
},
{
"Name": "Sticker",
"Small Name": "of your choice",
"Full Name": "Sticker of your choice",
"Description": null,
"Fulfillment Description": "Available anywhere\n",
"Cost Hours": 2,
"id": "recvsJVFthUTnozTO",
"Image URL": "https://cloud-50x4zfjwx-hack-club-bot.vercel.app/0enjoy_1.png",
"Max Order Quantity": 10
},
{
"Name": "OpenAI credits",
"Small Name": null,
"Full Name": "OpenAI credits ",
"Description": null,
"Fulfillment Description": "Available anywhere\n",
"Cost Hours": 4,
"id": "recJN0RO9obEGqP6e",
"Image URL": "https://cloud-nozsvexvx-hack-club-bot.vercel.app/0universal_paperclips_title_screen.png",
"Max Order Quantity": 1
},
{
"Name": "Domain",
"Small Name": "for 1 year",
"Full Name": "Domain for 1 year",
"Description": null,
"Fulfillment Description": "Available anywhere\n",
"Cost Hours": 4,
"id": "reca4tNvbZ6JLTcUH",
"Image URL": "https://cloud-5ttcbislu-hack-club-bot.vercel.app/2screenshot_2024-06-16_at_19.34.55.png",
"Max Order Quantity": 10
},
{
"Name": "Notebook",
"Small Name": "from GitHub",
"Full Name": "Notebook from GitHub",
"Description": "A Denik Layflat from the [GitHub Shop](https://www.thegithubshop.com/1455318-00-denik-layflat-notebook)",
"Fulfillment Description": "Ships to any country, but you may need to pay customs fees. Order shipped through \n",
"Cost Hours": 5,
"id": "recPP2L3kPwDYmu0B",
"Image URL": "https://cloud-2wpezbt6a-hack-club-bot.vercel.app/0notebook.png",
"Max Order Quantity": 1
},
{
"Name": "Logic Analyzer",
"Small Name": null,
"Full Name": "Logic Analyzer ",
"Description": null,
"Fulfillment Description": "Available in US, IN, EU, CA, SG & AU. Fulfilled by Amazon.\n",
"Cost Hours": 5,
"id": "recYa7NF3Amqcvf0i",
"Image URL": "https://cloud-re8ou8f0i-hack-club-bot.vercel.app/0image.png",
"Max Order Quantity": 5
},
{
"Name": "Breadboard",
"Small Name": "+ jumper wires",
"Full Name": "Breadboard + jumper wires",
"Description": null,
"Fulfillment Description": "Available in US, IN, EU, CA, SG & AU. Fulfilled by Amazon.\n",
"Cost Hours": 6,
"id": "rec3kwLZQjCB84lpT",
"Image URL": "https://cloud-ogt9k4r4u-hack-club-bot.vercel.app/0breadboard.png",
"Max Order Quantity": 10
},
{
"Name": "Multimeter",
"Small Name": null,
"Full Name": "Multimeter ",
"Description": null,
"Fulfillment Description": "Available in US, IN, EU, CA, SG & AU. Fulfilled by Amazon.\n",
"Cost Hours": 7,
"id": "recDJkGhWEU60NmBV",
"Image URL": "https://cloud-fyxfbdwdl-hack-club-bot.vercel.app/0multimeter.png",
"Max Order Quantity": 5
},
{
"Name": "Arcade Ticket Counter",
"Small Name": "(Timer)",
"Full Name": "Arcade Ticket Counter (Timer)",
"Description": "(formerly the Hack Hour Clock)\n",
"Fulfillment Description": "Fulfilled by USPS. Customs fees may apply if outside the US.\n",
"Cost Hours": 7,
"id": "recFlQknheFuFs4e9",
"Image URL": "https://cloud-for73aw9b-hack-club-bot.vercel.app/019719_3.png",
"Max Order Quantity": 1
},
{
"Name": "Soldering iron",
"Small Name": "+ solder",
"Full Name": "Soldering iron + solder",
"Description": null,
"Fulfillment Description": "Available in US, IN, EU, CA, SG & AU. Fulfilled by Amazon.\n",
"Cost Hours": 8,
"id": "rec5ssvcMqrSKneac",
"Image URL": "https://cloud-amz50nt0a-hack-club-bot.vercel.app/0soldering_iron.png",
"Max Order Quantity": 5
},
{
"Name": "Pinecil",
"Small Name": null,
"Full Name": "Pinecil ",
"Description": null,
"Fulfillment Description": "Fulfilled by USPS. Customs fees may apply if outside the US.\n",
"Cost Hours": 14,
"id": "recSXzyxP636j4dee",
"Image URL": "https://cloud-3lhacr9fo-hack-club-bot.vercel.app/061ix8eet_dl._ac_sx679_.png",
"Max Order Quantity": 1
},
{
"Name": "YubiKey",
"Small Name": null,
"Full Name": "YubiKey ",
"Description": null,
"Fulfillment Description": "Fulfilled by USPS. Customs fees may apply if outside the US.\n",
"Cost Hours": 15,
"id": "rec1pCxBpw737NP3I",
"Image URL": "https://cloud-elfpck7gj-hack-club-bot.vercel.app/0screenshot_2024-06-14_at_07.42.35.png",
"Max Order Quantity": 5
},
{
"Name": "GitHub Keycaps",
"Small Name": "x8",
"Full Name": "GitHub Keycaps x8",
"Description": null,
"Fulfillment Description": "Fulfilled by USPS. Customs fees may apply if outside the US.\n",
"Cost Hours": 15,
"id": "recCEtkEWr6u8mCfd",
"Image URL": "https://cloud-q3hnx73my-hack-club-bot.vercel.app/01542509_z_copy.png",
"Max Order Quantity": 1
},
{
"Name": "Octocat",
"Small Name": "Plushie",
"Full Name": "Octocat Plushie",
"Description": null,
"Fulfillment Description": "Fulfilled by USPS. Customs fees may apply if outside the US.\n",
"Cost Hours": 15,
"id": "reczeivgpqIj7ajA2",
"Image URL": "https://cloud-n716uf1pe-hack-club-bot.vercel.app/0gh_0008_z_dd7b_copy.png",
"Max Order Quantity": 1
},
{
"Name": "Wacom",
"Small Name": "Intuos S",
"Full Name": "Wacom Intuos S",
"Description": null,
"Fulfillment Description": "Available in US, IN, EU, CA, SG & AU. Fulfilled by Amazon.\n",
"Cost Hours": 25,
"id": "recoatqwqCXrsiAoz",
"Image URL": "https://cloud-bj39a8875-hack-club-bot.vercel.app/061pz9ub2wul._ac_sx679_.png",
"Max Order Quantity": 1
},
{
"Name": "Invertocat Backback",
"Small Name": "MIIR",
"Full Name": "Invertocat Backback MIIR",
"Description": null,
"Fulfillment Description": "Fulfilled by USPS. Customs fees may apply if outside the US.\n",
"Cost Hours": 50,
"id": "rec5i2AZtzHAG5znC",
"Image URL": "https://cloud-2wpezbt6a-hack-club-bot.vercel.app/1miir.png",
"Max Order Quantity": 1
},
{
"Name": "Flipper",
"Small Name": "Zero",
"Full Name": "Flipper Zero",
"Description": null,
"Fulfillment Description": "Available in US & EU. Fulfilled by [Flipper Zero](https://shop.flipperzero.one/policies/shipping-policy).\n",
"Cost Hours": 70,
"id": "recDmVbngx6NdN6pA",
"Image URL": "https://cloud-p2gdpsmd3-hack-club-bot.vercel.app/0top.png",
"Max Order Quantity": 1
},
{
"Name": "Mechanical Keyboard",
"Small Name": "MX",
"Full Name": "Mechanical Keyboard MX",
"Description": null,
"Fulfillment Description": "Fulfilled by USPS. Customs fees may apply if outside the US.\n",
"Cost Hours": 75,
"id": "recrybqL6E6d4tzoe",
"Image URL": "https://cloud-gt96uxjmh-hack-club-bot.vercel.app/061__ok6aqtl._ac_uf894_1000_ql80_.png",
"Max Order Quantity": 1
},
{
"Name": "Framework",
"Small Name": "factory seconds",
"Full Name": "Framework factory seconds",
"Description": null,
"Fulfillment Description": "Available in [specific countries](https://knowledgebase.frame.work/what-countries-and-regions-do-you-ship-to-r1899ikiO). Ships via USPS customs fees may apply.\n",
"Cost Hours": 120,
"id": "reciRscdVv46bC7cf",
"Image URL": "https://cloud-elfpck7gj-hack-club-bot.vercel.app/1screenshot_2024-06-14_at_07.39.22.png",
"Max Order Quantity": 1
},
{
"Name": "Prusa",
"Small Name": "MINI+",
"Full Name": "Prusa MINI+",
"Description": null,
"Fulfillment Description": "Available in US & EU. Customs fees outside the EU [may apply](https://help.prusa3d.com/article/vat-value-added-tax-customs-fees_1505#orders-outside-eu).\n",
"Cost Hours": 130,
"id": "recCeMp5iK7tRpqc3",
"Image URL": "https://cloud-cqnd9gu78-hack-club-bot.vercel.app/1prusa.png",
"Max Order Quantity": 1
},
{
"Name": "Bambu Lab",
"Small Name": "A1 mini",
"Full Name": "Bambu Lab A1 mini",
"Description": null,
"Fulfillment Description": "Available in US, CA, & EU. Shipping provided by Bambu Lab.\n",
"Cost Hours": 135,
"id": "rec58YSzn7V5x1GPR",
"Image URL": "https://cloud-n8ijhwk64-hack-club-bot.vercel.app/0a1_mini.png",
"Max Order Quantity": 1
},
{
"Name": "Framework",
"Small Name": "13 inch",
"Full Name": "Framework 13 inch",
"Description": null,
"Fulfillment Description": "Available in [specific countries](https://knowledgebase.frame.work/what-countries-and-regions-do-you-ship-to-r1899ikiO). Ships via USPS customs fees may apply.\n",
"Cost Hours": 175,
"id": "recj3qt349e2KXW6X",
"Image URL": "https://cloud-g0bjmr0sz-hack-club-bot.vercel.app/013in.png",
"Max Order Quantity": 1
},
{
"Name": "Quest 3",
"Small Name": null,
"Full Name": "Quest 3 ",
"Description": null,
"Fulfillment Description": "Available in [specific countries](https://www.meta.com/help/orders-and-returns/articles/quest-supported-countries/). Fulfilled by Meta.\n",
"Cost Hours": 200,
"id": "recuol7Uk5Z2sjViv",
"Image URL": "https://cloud-7x2qyu0b9-hack-club-bot.vercel.app/0screenshot_2024-06-14_at_08.46.20.png",
"Max Order Quantity": 1
},
{
"Name": "Framework",
"Small Name": "16 inch",
"Full Name": "Framework 16 inch",
"Description": null,
"Fulfillment Description": "Available in [specific countries](https://knowledgebase.frame.work/what-countries-and-regions-do-you-ship-to-r1899ikiO). Ships via USPS customs fees may apply.\n",
"Cost Hours": 400,
"id": "rec9cXQSlAJVnfJkv",
"Image URL": "https://cloud-cqnd9gu78-hack-club-bot.vercel.app/0framework.png",
"Max Order Quantity": 1
},
{
"Name": "MacBook",
"Small Name": "Air M2",
"Full Name": "MacBook Air M2",
"Description": null,
"Fulfillment Description": "Available in any country Apple ships to (e.g. US or IN).\n",
"Cost Hours": 400,
"id": "recFT8SlSOxFrG9ha",
"Image URL": "https://cloud-9zwbzfbtw-hack-club-bot.vercel.app/00image_from_ios-removebg-preview.png",
"Max Order Quantity": 1
}
]}
// FIXME: does not connect to Airtable (for local development)
export default async function handler(req, res) {
const data = await shopParts()
const filteredData = data.filter(record => record["Enabled"]).map(record => {
const filteredData = data.map(record => {
return {
name: record['Name'],
smallName: record['Small Name'],

89
pages/arcade/shop.js Normal file
View file

@ -0,0 +1,89 @@
import ShopComponent from "../../components/arcade/shop-component"
import { shopParts } from "../api/arcade/shop"
import { Link, Text } from 'theme-ui'
import { Balancer } from "react-wrap-balancer"
import Meta from '@hackclub/meta'
import Head from 'next/head'
/** @jsxImportSource theme-ui */
const styled = `
@import url('https://fonts.googleapis.com/css2?family=Slackey&family=Emblema+One&family=Gaegu&display=swap');
.slackey {
font-family: "Slackey", sans-serif;
}
.gaegu {
font-family: "Gaegu", sans-serif;
}
body {
background-color: #FAEFD6;
}
`
export default function Shop({ availableItems, userAirtableID = null, hoursBalance = 0 }) {
return (
<>
<Meta
as={Head}
title="Arcade Shop"
description="Check out the prizes at the Arcade Shop!"
image="https://cloud-luaw423i2-hack-club-bot.vercel.app/0frame_33__1_.png"
/>
<style>
{`
._title-container {
width: 100%;
}
`}
</style>
<Balancer className="_title-container">
<h1
sx={{
textAlign: 'center',
fontSize: 5,
color: '#FF8C37',
my: 0,
pt: 5,
display: 'block',
width: '100%'
}}
className="slackey"
>
Welcome to the shop
</h1>
</Balancer>
<Text sx={{ display: 'block', textAlign: 'center', color: '#35290F' }} className='gaegu' variant='subtitle' >Like what you see? Check out <Link href="/arcade">the Hack Club Arcade!</Link></Text>
<ShopComponent availableItems={availableItems} />
</>
)
}
export async function getStaticProps() {
const props = {};
await Promise.all([
shopParts().then(items => {
// FIXME: should be filtered by item['Enabled'] in production
const availableItems = items.map(item => ({
'Name': item['Name'] || null,
'Small Name': item['Small Name'] || null,
'Full Name': item['Full Name'] || null,
'Description': item['Description'] || null,
'Fulfillment Description': item['Fulfillment Description'] || null,
'Cost Hours': item['Cost Hours'] || 0,
id: item.id,
'Image URL': item['Image URL'] || null,
'Max Order Quantity': item['Max Order Quantity'] || 1
}))
props.availableItems = availableItems
}),
])
return { props, revalidate: 10 }
}