├── .github └── workflows │ └── pages.yml ├── .gitignore ├── .replit ├── .vscode └── settings.json ├── README.md ├── components ├── Auth.js ├── Inputs │ ├── Select.js │ ├── Select.module.css │ ├── Text.js │ ├── Text.module.css │ ├── Title.js │ └── Title.module.css ├── Modal.js └── Sidebar.js ├── docs ├── 404.html ├── _next │ └── static │ │ ├── 63naq6OZNUwEmbBG38Fkb │ │ ├── _buildManifest.js │ │ └── _ssgManifest.js │ │ ├── chunks │ │ ├── 7bad3855-0bb9e0fb6954a08c.js │ │ ├── framework-9b5d6ec4444c80fa.js │ │ ├── main-3123a443c688934f.js │ │ ├── pages │ │ │ ├── _app-fd9d1b23c3421e47.js │ │ │ ├── _error-7397496ca01950b1.js │ │ │ └── index-c7c2ac66f3a267fe.js │ │ ├── polyfills-c67a75d1b6f99dc8.js │ │ └── webpack-7ee66019f7f6d30f.js │ │ └── css │ │ ├── 5599a730af22f4f4.css │ │ └── bd23b02b3e2210dc.css ├── favicon.ico ├── index.html ├── orange.png ├── orange.svg ├── vercel.svg └── white.svg ├── lib ├── caSchools.js ├── questions.js └── schoolsList.js ├── next.config.js ├── package.json ├── pages ├── _app.js ├── api │ ├── discord │ │ └── link.js │ ├── donor.js │ ├── email │ │ └── [city] │ │ │ └── [email].js │ ├── hello.js │ ├── posts.js │ ├── register.js │ ├── scanner.js │ ├── scrapbookdelete.js │ ├── scrapbookpost.js │ ├── shippost.js │ ├── ships.js │ ├── v2.js │ └── webhook.js ├── files │ └── [file].js ├── index.js ├── register-old.js ├── register.js ├── registration │ ├── discord-success.js │ └── success.js ├── schedule.js ├── scrapbook.js └── ships.js ├── public ├── favicon-old-2.png ├── favicon-old.png ├── favicon-vercel.ico ├── favicon.png ├── flyer2.pdf ├── items │ └── PressRelease-03-13-2023.pdf ├── logo-full-light.png ├── map.png ├── orange.png ├── orange.svg ├── press │ └── 1.pdf ├── prospectus.pdf ├── social.png ├── sponsor-assets │ ├── bank.svg │ └── vercel.svg ├── thumbnail.png ├── vercel.svg └── white.svg ├── replit.nix ├── replit.sh ├── styles ├── Home.module.css ├── Register.module.css └── globals.css ├── utils ├── cssUtils.js ├── useMedia.js └── useProtection.js └── yarn.lock /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Next.js site to GitHub Pages 2 | # 3 | # To get started with Next.js see: https://nextjs.org/docs/getting-started 4 | # 5 | name: Deploy Next.js site to Pages 6 | 7 | on: 8 | # Runs on pushes targeting the default branch 9 | push: 10 | branches: ["main"] 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 16 | permissions: 17 | contents: read 18 | pages: write 19 | id-token: write 20 | 21 | # Allow one concurrent deployment 22 | concurrency: 23 | group: "pages" 24 | cancel-in-progress: true 25 | 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | - name: Detect package manager 34 | id: detect-package-manager 35 | run: | 36 | if [ -f "${{ github.workspace }}/yarn.lock" ]; then 37 | echo "::set-output name=manager::yarn" 38 | echo "::set-output name=command::install" 39 | echo "::set-output name=runner::yarn" 40 | exit 0 41 | elif [ -f "${{ github.workspace }}/package.json" ]; then 42 | echo "::set-output name=manager::npm" 43 | echo "::set-output name=command::ci" 44 | echo "::set-output name=runner::npx --no-install" 45 | exit 0 46 | else 47 | echo "Unable to determine packager manager" 48 | exit 1 49 | fi 50 | - name: Setup Node 51 | uses: actions/setup-node@v3 52 | with: 53 | node-version: "16" 54 | cache: ${{ steps.detect-package-manager.outputs.manager }} 55 | - name: Setup Pages 56 | uses: actions/configure-pages@v2 57 | with: 58 | # Automatically inject basePath in your Next.js configuration file and disable 59 | # server side image optimization (https://nextjs.org/docs/api-reference/next/image#unoptimized). 60 | # 61 | # You may remove this line if you want to manage the configuration yourself. 62 | static_site_generator: next 63 | - name: Restore cache 64 | uses: actions/cache@v3 65 | with: 66 | path: | 67 | .next/cache 68 | # Generate a new cache whenever packages or source files change. 69 | key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} 70 | # If source files changed but packages didn't, rebuild from a prior cache. 71 | restore-keys: | 72 | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- 73 | - name: Install dependencies 74 | run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} 75 | - name: Build with Next.js 76 | run: ${{ steps.detect-package-manager.outputs.runner }} next build 77 | - name: Static HTML export with Next.js 78 | run: ${{ steps.detect-package-manager.outputs.runner }} next export 79 | - name: Upload artifact 80 | uses: actions/upload-pages-artifact@v1 81 | with: 82 | path: ./out 83 | 84 | # Deployment job 85 | deploy: 86 | environment: 87 | name: github-pages 88 | url: ${{ steps.deployment.outputs.page_url }} 89 | runs-on: ubuntu-latest 90 | needs: build 91 | steps: 92 | - name: Deploy to GitHub Pages 93 | id: deployment 94 | uses: actions/deploy-pages@v1 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | 2 | hidden = [".config", "package-lock.json"] 3 | run = "./replit.sh" 4 | 5 | [[hints]] 6 | regex = "Error \\[ERR_REQUIRE_ESM\\]" 7 | message = "We see that you are using require(...) inside your code. We currently do not support this syntax. Please use 'import' instead when using external modules. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)" 8 | 9 | [nix] 10 | channel = "stable-22_11" 11 | 12 | [env] 13 | XDG_CONFIG_HOME = "/home/runner/.config" 14 | PATH = "/home/runner/$REPL_SLUG/.config/npm/node_global/bin:/home/runner/$REPL_SLUG/node_modules/.bin" 15 | npm_config_prefix = "/home/runner/$REPL_SLUG/.config/npm/node_global" 16 | 17 | [gitHubImport] 18 | requiredFiles = [".replit", "replit.nix", ".config", "package.json", "package-lock.json"] 19 | 20 | [packager] 21 | language = "nodejs" 22 | 23 | [packager.features] 24 | packageSearch = true 25 | guessImports = true 26 | enabledForHosting = false 27 | 28 | [unitTest] 29 | language = "nodejs" 30 | 31 | [debugger] 32 | support = true 33 | 34 | [debugger.interactive] 35 | transport = "localhost:0" 36 | startCommand = [ "dap-node" ] 37 | 38 | [debugger.interactive.initializeMessage] 39 | command = "initialize" 40 | type = "request" 41 | 42 | [debugger.interactive.initializeMessage.arguments] 43 | clientID = "replit" 44 | clientName = "replit.com" 45 | columnsStartAt1 = true 46 | linesStartAt1 = true 47 | locale = "en-us" 48 | pathFormat = "path" 49 | supportsInvalidatedEvent = true 50 | supportsProgressReporting = true 51 | supportsRunInTerminalRequest = true 52 | supportsVariablePaging = true 53 | supportsVariableType = true 54 | 55 | [debugger.interactive.launchMessage] 56 | command = "launch" 57 | type = "request" 58 | 59 | [debugger.interactive.launchMessage.arguments] 60 | args = [] 61 | console = "externalTerminal" 62 | cwd = "." 63 | environment = [] 64 | pauseForSourceMap = false 65 | program = "./index.js" 66 | request = "launch" 67 | sourceMaps = true 68 | stopOnEntry = false 69 | type = "pwa-node" 70 | 71 | [languages] 72 | 73 | [languages.javascript] 74 | pattern = "**/{*.js,*.jsx,*.ts,*.tsx}" 75 | 76 | [languages.javascript.languageServer] 77 | start = "typescript-language-server --stdio" 78 | 79 | [deployment] 80 | run = ["sh", "-c", "node index.js"] 81 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.background": "#302A42", 4 | "titleBar.activeBackground": "#433B5C", 5 | "titleBar.activeForeground": "#FCFCFD" 6 | } 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hack OC Website 2 | 3 | Website for Orange County's first high school hackathon! 4 | 5 | Built with next.js and deployed on Vercel 6 | -------------------------------------------------------------------------------- /components/Auth.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export function useDeviceId () { 4 | const [deviceId, setDeviceId] = useState(null); 5 | 6 | useEffect(() => { 7 | if (localStorage.getItem('deviceId')) { 8 | setDeviceId(localStorage.getItem('deviceId')); 9 | } else { 10 | const newDeviceId = Math.random().toString(36).substr(2, 9); 11 | localStorage.setItem('deviceId', newDeviceId); 12 | setDeviceId(newDeviceId); 13 | } 14 | }, []); 15 | 16 | const loading = deviceId === null; 17 | 18 | const waitForDeviceId = () => new Promise((resolve, reject) => { 19 | if (deviceId) resolve(deviceId); 20 | else { 21 | const interval = setInterval(() => { 22 | if (deviceId) { 23 | clearInterval(interval); 24 | resolve(deviceId); 25 | } 26 | }, 100); 27 | } 28 | }); 29 | 30 | return [deviceId, waitForDeviceId, loading]; 31 | } -------------------------------------------------------------------------------- /components/Inputs/Select.js: -------------------------------------------------------------------------------- 1 | import { useState, uesEffet, useEffect, useRef } from 'react'; 2 | import styles from './Select.module.css'; 3 | 4 | function defaultValidate (chips) { 5 | return chips?.length; 6 | } 7 | 8 | function isVisible (ele, container) { 9 | if (!container) container = ele.parentElement; 10 | 11 | const eleTop = ele.offsetTop; 12 | const eleBottom = eleTop + ele.clientHeight; 13 | 14 | const containerTop = container.scrollTop; 15 | const containerBottom = containerTop + container.clientHeight; 16 | 17 | return ( 18 | (eleTop >= containerTop && eleBottom <= containerBottom) || 19 | (eleTop < containerTop && containerTop < eleBottom) || 20 | (eleTop < containerBottom && containerBottom < eleBottom) 21 | ); 22 | } 23 | 24 | function scrollParentToChild (child, parent) { 25 | if (!parent) parent = child.parentElement; 26 | let parentRect = parent.getBoundingClientRect(); 27 | let parentViewableArea = { 28 | height: parent.clientHeight, 29 | width: parent.clientWidth 30 | }; 31 | 32 | let childRect = child.getBoundingClientRect(); 33 | let isViewable = (childRect.top >= parentRect.top) && (childRect.bottom <= parentRect.top + parentViewableArea.height); 34 | 35 | if (!isViewable) { 36 | const scrollTop = childRect.top - parentRect.top; 37 | const scrollBot = childRect.bottom - parentRect.bottom; 38 | if (Math.abs(scrollTop) < Math.abs(scrollBot)) parent.scrollTop += scrollTop; 39 | else parent.scrollTop += scrollBot; 40 | } 41 | } 42 | 43 | const modify = str => str.toLowerCase().split('').filter(a => `abcdefghijklmnopqrstuvwxyz1234567890`.includes(a)).join('') ?? ''; 44 | 45 | function looseMatch (str1, str2) { 46 | return modify(str1) == modify(str2); 47 | } 48 | 49 | export function Chip ({ chipData: chip, forceUpdate, chips, setChips, multiSelect, presetChips, localData, isCustom, isSelected, resetSelected, selectThisChip, setActiveColor, setLocalData, startEdits }) { 50 | const selfRef = useRef(null); 51 | 52 | useEffect(() => { 53 | if (!isVisible(selfRef.current)) { 54 | scrollParentToChild(selfRef.current); 55 | } 56 | window.looseMatch = looseMatch; 57 | }, [isSelected]); 58 | 59 | return ( 60 |
e.preventDefault()} onMouseUp={e => { 61 | if (isCustom) { 62 | e.preventDefault(); 63 | resetSelected(0); 64 | let theseChips = JSON.parse(JSON.stringify(chips)); 65 | theseChips.push({ 66 | name: localData, 67 | color: chip.color, 68 | id: Math.floor(Math.random() * 10000) + '-' + Date.now() 69 | }); 70 | setChips(theseChips); 71 | setActiveColor(Math.random() * 6 | 0); 72 | setLocalData(''); 73 | forceUpdate(); 74 | startEdits(); 75 | } else { 76 | e.preventDefault(); 77 | resetSelected(0); 78 | let index = 0; 79 | presetChips.forEach((c, i) => (c.name == chip.name ? index = i : 0)); 80 | let thesePresetChips = JSON.parse(JSON.stringify(presetChips)); 81 | let theseChips = chips; 82 | let thisChip = thesePresetChips.splice(index, 1)[0]; 83 | if (multiSelect) theseChips = theseChips.concat([{ 84 | name: thisChip.name, 85 | color: thisChip.color, 86 | id: Math.floor(Math.random() * 10000) + '-' + Date.now() 87 | }]); 88 | else theseChips = [ 89 | { 90 | name: thisChip.name, 91 | color: thisChip.color, 92 | id: Math.floor(Math.random() * 10000) + '-' + Date.now() 93 | } 94 | ]; 95 | setChips(theseChips); 96 | forceUpdate(); 97 | startEdits(); 98 | setLocalData(''); 99 | } 100 | }}> 101 | {isCustom && 'Add '} {chip.name} × 102 |
103 | ); 104 | } 105 | 106 | export default function Select (props) { 107 | const { setValue, multi, options = [], special, validate: _validate, name, description, help, placeholder, data, setData, initialData, wrapperClass, width, margin, type, id: _id, required, dontDisplayAll } = props; 108 | 109 | const displayAll = !dontDisplayAll; 110 | 111 | const [selected, setSelected] = useState(0); 112 | 113 | const id = _id ?? name?.toLowerCase()?.split(' ')?.join('-')?.split('')?.filter(a => `abcdefghijklmnopqrstuvwxyz1234567890-_`.includes(a))?.join('') ?? 'error'; 114 | const validate = data => (_validate ?? defaultValidate)() && (required ? data?.length : true); 115 | 116 | const [localData, setLocalData] = useState(initialData ?? ''); 117 | const [valid, setValid] = useState(false); 118 | const [partiallyValid, setPartiallyValid] = useState(false); 119 | 120 | const inputRef = useRef(null); 121 | const dropdownRef = useRef(null); 122 | 123 | const custom = options.filter(opt => opt?.custom).length > 0; 124 | const multiSelect = multi; 125 | 126 | const presetChips = options.filter(opt => !opt?.custom).map((opt, i) => ({ 127 | name: opt, 128 | color: i % 7, 129 | })); 130 | 131 | const [displayedPresetChips, setDisplayedPresetChips] = useState(presetChips); 132 | 133 | const [chips, setChips] = useState([ 134 | ]); 135 | 136 | useEffect(() => { 137 | setValue(name, chips.map(c => c.name)); 138 | }, [chips]); 139 | 140 | const [activeColor, setActiveColor] = useState(0); 141 | 142 | const [counter, setCounter] = useState(Math.random() * 6 | 0); 143 | 144 | useEffect(() => { 145 | let excluded = presetChips.filter(presetChip => !chips.map(chip => chip.name).includes(presetChip.name)); 146 | if (localData?.length) { 147 | setDisplayedPresetChips( 148 | excluded.filter(chip => ( 149 | modify(chip.name).includes(modify(localData)) 150 | || !modify(localData).split('').filter((a, i) => chip.name.toLowerCase().split(' ')[i]?.[0] !== a).length 151 | )) 152 | ); 153 | } else { 154 | setDisplayedPresetChips(excluded); 155 | } 156 | }, [chips, localData, counter]); 157 | 158 | function forceUpdate () { 159 | setCounter(counter + 1); 160 | } 161 | 162 | const [editing, setEditing] = useState(false); 163 | 164 | function startEdits () { 165 | setEditing(true); 166 | } 167 | 168 | function finishEdits () { 169 | setEditing(false); 170 | } 171 | 172 | function resetSelected () { 173 | setSelected(0); 174 | } 175 | 176 | useEffect(() => { 177 | let validationStatus = validate(chips); 178 | setValid(!editing && validationStatus); 179 | setPartiallyValid(editing && validationStatus); 180 | }, [chips, editing, counter]); 181 | 182 | const displayedChips = [ 183 | ...(displayedPresetChips.filter(chip => looseMatch(chip.name, localData))[0] ? [displayedPresetChips.filter(chip => looseMatch(chip.name, localData))[0]] : []).map(chip => ({ chip })), 184 | ...displayedPresetChips.filter(chip => !looseMatch(chip.name, localData)).map(chip => ({ chip })), 185 | ...((localData?.length && custom) ? [{ 186 | name: localData?.trim(), 187 | color: activeColor 188 | }] : []).map(chip => ({ chip, isCustom: true })) 189 | ]; 190 | 191 | return ( 192 | <> 193 |
l).join(' ')} style={{ 194 | width: width ?? '300px', 195 | margin: margin ?? '0px', 196 | boxSizing: 'border-box' 197 | }}> 198 | 199 |

{description}

200 |
(e.preventDefault(), inputRef.current.focus(), dropdownRef.current.scrollTop = 0, startEdits(), setSelected(0))} tabIndex={-1} onKeyDown={e => { 201 | if (e.key == 'ArrowDown') { 202 | setSelected((selected + 1 + displayedChips.length) % displayedChips.length); 203 | } else if (e.key == 'ArrowUp') { 204 | setSelected((selected - 1 + displayedChips.length) % displayedChips.length); 205 | } 206 | }}> 207 | {chips.map(chip => ( 208 | e.preventDefault()} data-color={chip.color} className={styles.chip} key={chip.id + chip.name + chip.color}>{chip.name} { 209 | let index = 0; 210 | chips.forEach((c, i) => (c.id == chip.id ? index = i : 0)); 211 | let theseChips = JSON.parse(JSON.stringify(chips)); 212 | theseChips.splice(index, 1); 213 | setChips(theseChips); 214 | }}>× 215 | ))} 216 | { 217 | setLocalData(e.target.value); 218 | setSelected(0); 219 | if (setData instanceof Function) setData(e.target.value); 220 | }} onKeyDown={e => { 221 | if (e.key == 'Backspace' && e.target.value?.length == 0) { 222 | let theseChips = JSON.parse(JSON.stringify(chips)); 223 | theseChips.pop(); 224 | startEdits(); 225 | setChips(theseChips); 226 | } else if ((e.key == 'Enter' || e.key == ',')) { 227 | let activeChip = displayedChips[selected]?.chip; 228 | if (!activeChip) return; 229 | if (e.key == ',') e.preventDefault(); 230 | let theseChips = JSON.parse(JSON.stringify(chips)); 231 | if (!displayedPresetChips.length && !custom) return; 232 | if (multiSelect) theseChips.push({ 233 | name: activeChip.name, 234 | color: activeChip.color, 235 | id: localData.toLowerCase().split(' ').join('-') 236 | }); 237 | else theseChips = [ 238 | { 239 | name: activeChip.name, 240 | color: activeChip.color, 241 | id: localData.toLowerCase().split(' ').join('-') 242 | } 243 | ]; 244 | setActiveColor(Math.random() * 6 | 0); 245 | setChips(theseChips); 246 | e.target.value = ''; 247 | setLocalData(''); 248 | startEdits(); 249 | resetSelected(); 250 | if (setData instanceof Function) setData(''); 251 | } 252 | }} 253 | name={id} id={id} type={type} ref={inputRef} value={localData}> 254 |
255 | 256 |
inputRef.current.focus()} onMouseDown={startEdits}> 257 | {displayAll ? displayedChips.map(({ chip, isCustom }, i) => setSelected(i), 268 | setActiveColor, 269 | setLocalData, 270 | startEdits 271 | }} />) : displayedChips.filter(({ isCustom }, i) => isCustom || i < 6 && localData?.length).map(({ chip, isCustom }, i) => setSelected(i), 282 | setActiveColor, 283 | setLocalData, 284 | startEdits 285 | }} />)} 286 | {!displayAll && !localData?.length &&
Type to search.
} 287 |
288 |
289 | 290 | ) 291 | } -------------------------------------------------------------------------------- /components/Inputs/Select.module.css: -------------------------------------------------------------------------------- 1 | .wrapper div.content > * { 2 | outline: none; 3 | display: inline-block; 4 | border: none; 5 | font-size: 1em; 6 | font-weight: 500; 7 | font-family: system-ui, Inter; 8 | } 9 | 10 | .wrapper div.content input { 11 | flex-grow: 1; 12 | min-width: 100px; 13 | flex: 0 0 auto; 14 | } 15 | 16 | .wrapper div.content { 17 | width: 100%; 18 | border-radius: 0.25em; 19 | border-color: #d3d3d4; 20 | transition: 0.2s; 21 | border-width: 2px; 22 | border-style: solid; 23 | font-size: 1em; 24 | display: flex; 25 | flex-wrap: wrap; 26 | gap: 0.5rem; 27 | padding: 0.5rem; 28 | font-weight: 500; 29 | font-family: system-ui, Inter; 30 | z-index: 20; 31 | position: relative; 32 | } 33 | 34 | .wrapper div.content:hover { 35 | border-color: #b7b7b8; 36 | } 37 | 38 | .wrapper.isValid:not(:focus-within) > span { 39 | filter: opacity(1); 40 | } 41 | 42 | .wrapper.isValid:focus-within > span { 43 | filter: opacity(0.6) grayscale(0.6); 44 | } 45 | 46 | .wrapper.isPartiallyValid > span { 47 | filter: opacity(0.6) grayscale(0.6); 48 | } 49 | 50 | .wrapper div.content[type="date"] + span { 51 | transform: translate(-26px, -1px); 52 | } 53 | 54 | .wrapper label { 55 | margin-bottom: 0.1em; 56 | font-weight: 600; 57 | } 58 | 59 | .wrapper p { 60 | margin-top: 0em; 61 | margin-bottom: 0.5em; 62 | color: #767676; 63 | font-size: 14px; 64 | font-weight: 500; 65 | } 66 | 67 | .wrapper div.content:focus-within { 68 | outline: none; 69 | border-color: var(--orange); 70 | box-shadow: 0 0 0 2px rgba(var(--orange-3-values), 0.2) 71 | } 72 | 73 | .wrapper { 74 | min-width: 200px; 75 | font-family: system-ui, Inter; 76 | position: relative; 77 | } 78 | 79 | .content:not(:has(> .chip)):not(:focus-within), .content:not(:has(> .chip)):not(:focus-within) * { 80 | cursor: pointer!important; 81 | } 82 | 83 | .content:has(> .chip) { 84 | cursor: text; 85 | } 86 | 87 | .content:has(> .chip)::after { 88 | content: '▼'; 89 | position: absolute; 90 | top: 50%; 91 | font-weight: 200; 92 | right: 10px; 93 | transform: translateY(-50%) scale(0.75); 94 | color: #767676; 95 | filter: opacity(0); 96 | transition: 0.2s all; 97 | } 98 | 99 | .content:not(:has(> .chip))::after { 100 | content: '▼'; 101 | position: absolute; 102 | top: 50%; 103 | font-weight: 200; 104 | right: 10px; 105 | transform: translateY(-50%) scale(0.75); 106 | color: #767676; 107 | filter: opacity(1)!important; 108 | transition: 0.2s all; 109 | } 110 | 111 | .content:focus-within { 112 | cursor: text; 113 | } 114 | 115 | .wrapper > .content:has(> .chip) ~ span { 116 | bottom: 12px; 117 | right: 12px; 118 | } 119 | 120 | .wrapper > span { 121 | color: #21ca71; 122 | font-weight: 700; 123 | font-size: 20px; 124 | position: absolute; 125 | display: inline-block; 126 | filter: opacity(0); 127 | right: 10px; 128 | bottom: 8px; 129 | pointer-events: none; 130 | transition: 0.1s filter; 131 | } 132 | 133 | .wrapper div.content::placeholder { 134 | color: rgb(157, 157, 157); 135 | opacity: 1; 136 | } 137 | 138 | .wrapper > label > span { 139 | width: 20px; 140 | height: 20px; 141 | padding: 4px; 142 | cursor: help; 143 | border-radius: 10px; 144 | box-sizing: border-box; 145 | font-size: 10px; 146 | vertical-align: middle; 147 | display: inline-block; 148 | position: relative; 149 | transform: translateY(-2px); 150 | } 151 | 152 | .wrapper > label > span, .chip, .wrapper > .content { 153 | -webkit-touch-callout: none; 154 | -webkit-user-select: none; 155 | -khtml-user-select: none; 156 | -moz-user-select: none; 157 | -ms-user-select: none; 158 | user-select: none; 159 | } 160 | 161 | .wrapper > .content > * { 162 | -webkit-touch-callout: all; 163 | -webkit-user-select: all; 164 | -khtml-user-select: all; 165 | -moz-user-select: all; 166 | -ms-user-select: all; 167 | user-select: all; 168 | } 169 | 170 | .wrapper > label > span > span { 171 | width: 12px; 172 | height: 12px; 173 | display: inline-block; 174 | background: rgba(51, 124, 227, 0.769); 175 | border-radius: 6px; 176 | color: white; 177 | font-size: 10px; 178 | font-weight: bold; 179 | text-align: center; 180 | position: relative; 181 | outline: none; 182 | } 183 | 184 | .wrapper > label > span > span:after { 185 | opacity: 0; 186 | color: white; 187 | letter-spacing: 0; 188 | line-height: 1.375; 189 | transform: translateY(50%); 190 | box-shadow: 0 0 2px 0 #00000010, 191 | 0 4px 8px 0 #00000020; 192 | content: attr(aria-label); 193 | z-index: 10; 194 | left: 100%; 195 | bottom: 50%; 196 | min-height: 20px; 197 | position: absolute; 198 | padding: 0.25rem 0.75rem; 199 | background-color: #000000df; 200 | border-radius: 6px; 201 | font-family: Inter; 202 | font-size: 14px; 203 | max-width: 240px; 204 | font-weight: 500; 205 | transition: 0.15s all ease; 206 | width: max-content; 207 | height: min-content; 208 | right: 0; 209 | pointer-events: none; 210 | word-wrap: break-word; 211 | text-align: center; 212 | margin-left: 0.5rem; 213 | } 214 | 215 | .wrapper > label > span:hover > span:after, 216 | .wrapper > label > span:active > span:after, 217 | .wrapper > label > span:focus > span:after, 218 | .wrapper > label > span:focus-within > span:after { 219 | outline: none; 220 | z-index: 9; 221 | opacity: 1; 222 | } 223 | 224 | .wrapper > label > span:focus-within { 225 | background: rgba(51, 124, 227, 0.2); 226 | } 227 | 228 | .dropdown { 229 | position: absolute; 230 | top: calc( 100% + 2px ); 231 | left: 0%; 232 | width: 100%; 233 | border-radius: 0.25em; 234 | border-color: #d3d3d4; 235 | transition: 0.15s ease-out; 236 | border-width: 2px; 237 | border-style: solid; 238 | font-size: 1em; 239 | cursor: default; 240 | font-weight: 500; 241 | font-family: Inter; 242 | overflow-y: hidden; 243 | z-index: 30; 244 | background-color: white; 245 | height: 0px; 246 | pointer-events: none; 247 | filter: opacity(0); 248 | /* box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; */ 249 | /* box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; */ 250 | box-shadow: rgba(50, 50, 93, 0.25) 0px 30px 60px -12px, rgba(0, 0, 0, 0.3) 0px 18px 36px -18px; 251 | } 252 | 253 | .wrapper:focus-within > div.content ~ .dropdown { 254 | display: block; 255 | height: 220px; 256 | top: 100%; 257 | overflow-y: auto; 258 | filter: opacity(1); 259 | 260 | pointer-events: all; 261 | 262 | } 263 | 264 | .wrapper:focus-within > div.content ~ .dropdown:has(> .chipOption:nth-child(3)) { 265 | height: 220px; 266 | } 267 | 268 | .wrapper:focus-within > div.content ~ .dropdown:not(:has(> .chipOption:nth-child(3))) { 269 | height: 150px; 270 | } 271 | 272 | .chip { 273 | background: #ffe2dd; 274 | border-radius: 12px; 275 | padding: 0.25rem 0.4rem; 276 | font-size: 0.85rem; 277 | padding-right: 24px; 278 | position: relative; 279 | cursor: default; 280 | } 281 | 282 | .chip[data-color="1"] { 283 | background: #fbdec9; 284 | } 285 | 286 | .chip[data-color="2"] { 287 | background: #feecc8; 288 | } 289 | 290 | .chip[data-color="3"] { 291 | background: #dbeddb; 292 | } 293 | 294 | .chip[data-color="4"] { 295 | background: #d3e5ef; 296 | } 297 | 298 | .chip[data-color="5"] { 299 | background: #e8deee; 300 | } 301 | 302 | .chip[data-color="6"] { 303 | background: #f5e1e9; 304 | } 305 | 306 | .chip .close { 307 | position: absolute; 308 | right: 0.25rem; 309 | top: 50%; 310 | transform: translateY(-50%); 311 | vertical-align: middle; 312 | color: rgba(26, 26, 26, 0.651); 313 | height: 1.25rem; 314 | width: 1.25rem; 315 | border-radius: 50%; 316 | display: flex; 317 | justify-content: center; 318 | align-items: center; 319 | cursor: pointer; 320 | transition: 0.2s all; 321 | } 322 | 323 | .dropdown .chip .close { 324 | display: none; 325 | } 326 | 327 | .dropdown .chip { 328 | padding-right: 0.4rem; 329 | display: inline-block; 330 | border-radius: 12px; 331 | cursor: pointer; 332 | } 333 | 334 | .chip .close:hover { 335 | color: rgba(10, 10, 10, 0.922); 336 | } 337 | 338 | .chipOption { 339 | display: block; 340 | padding: 0.65rem 0.4rem; 341 | cursor: pointer; 342 | } 343 | 344 | .dropdown:not(:hover):not(> .selectedChip) .chipOption:first-child { 345 | background: #efefef; 346 | } 347 | 348 | .selectedChip { 349 | background: #efefef; 350 | } 351 | 352 | .dropdown:not(> .selectedChip) .chipOption:hover { 353 | background: #efefef; 354 | } 355 | 356 | .dropdown:not(> .selectedChip) .chipOption:first-child:hover, .chipOption:first-child.selectedChip { 357 | background: #efefef; 358 | } 359 | 360 | .chipOption:active { 361 | background: #dfdfde!important; 362 | } -------------------------------------------------------------------------------- /components/Inputs/Text.js: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { useState, uesEffet, useEffect, useRef } from 'react'; 3 | import styles from './Text.module.css'; 4 | 5 | function defaultValidate (text) { 6 | return text?.length; 7 | } 8 | 9 | export default function Text (props) { 10 | const { setValue, validate: _validate, name, description, help, placeholder, data, setData, initialData, wrapperClass, width, margin, type, id: _id, required } = props; 11 | 12 | const id = _id ?? name?.toLowerCase()?.split(' ')?.join('-')?.split('')?.filter(a => `abcdefghijklmnopqrstuvwxyz1234567890-_`.includes(a))?.join('') ?? 'error'; 13 | const validate = data => (_validate ? _validate(data) : true) && (required ? data?.length : true); 14 | 15 | const [localData, setLocalData] = useState(initialData ?? ''); 16 | const [valid, setValid] = useState(false); 17 | const [partiallyValid, setPartiallyValid] = useState(false); 18 | const ref = useRef(); 19 | useEffect(() => { 20 | setValue(name, localData); 21 | }, [localData]); 22 | 23 | function updateEmail (newEmail) { 24 | // ref.current.value = newEmail; 25 | setLocalData(newEmail); 26 | setPartiallyValid(validate(newEmail)); 27 | setValid(validate(newEmail)); 28 | 29 | } 30 | 31 | var router = useRouter(); 32 | useEffect(() => { 33 | 34 | if (router.query.email && name?.toLowerCase?.() === 'email') { 35 | updateEmail(router.query.email); 36 | } 37 | }, [router.query.email]) 38 | 39 | return ( 40 | <> 41 |
l).join(' ')} style={{ 42 | width: width ?? '300px', 43 | margin: margin ?? '0px', 44 | boxSizing: 'border-box' 45 | }}> 46 | 47 |

{description}

48 | { 49 | console.log(e.target.value); 50 | console.log(validate, validate(e.target.value)); 51 | console.log(_validate(e.target.value)); 52 | setLocalData(e.target.value); 53 | if (setData instanceof Function) setData(e.target.value); 54 | setPartiallyValid(validate(e.target.value)); 55 | }} onBlur={() => { 56 | setValid(validate(localData)); 57 | }} onFocus={() => { 58 | setValid(false); 59 | }} placeholder={placeholder}> 60 | 61 |
62 | 63 | ) 64 | } -------------------------------------------------------------------------------- /components/Inputs/Text.module.css: -------------------------------------------------------------------------------- 1 | .wrapper input { 2 | width: 100%; 3 | border-radius: 0.25em; 4 | border-color: #d3d3d4; 5 | transition: 0.2s; 6 | border-width: 2px; 7 | border-style: solid; 8 | padding: 0.5em; 9 | font-size: 1em; 10 | font-weight: 500; 11 | font-family: system-ui, Inter; 12 | } 13 | 14 | .wrapper input:hover { 15 | border-color: #b7b7b8; 16 | } 17 | 18 | .wrapper.isValid > span { 19 | filter: opacity(1); 20 | } 21 | 22 | .wrapper.isPartiallyValid > span { 23 | filter: opacity(0.6) grayscale(0.6); 24 | } 25 | 26 | .wrapper input[type="date"] + span { 27 | transform: translate(-26px, -1px); 28 | } 29 | 30 | .wrapper label { 31 | margin-bottom: 0.1em; 32 | font-weight: 600; 33 | } 34 | 35 | .wrapper p { 36 | margin-top: 0em; 37 | margin-bottom: 0.5em; 38 | color: #767676; 39 | font-size: 14px; 40 | font-weight: 500; 41 | } 42 | 43 | .wrapper input:focus { 44 | outline: none; 45 | border-color: var(--orange); 46 | box-shadow: 0 0 0 2px rgba(var(--orange-3-values), 0.2) 47 | } 48 | 49 | .wrapper { 50 | min-width: 200px; 51 | font-family: system-ui, Inter; 52 | position: relative; 53 | } 54 | 55 | .wrapper > span { 56 | color: #21ca71; 57 | font-weight: 700; 58 | font-size: 20px; 59 | position: absolute; 60 | display: inline-block; 61 | filter: opacity(0); 62 | right: 10px; 63 | bottom: 8px; 64 | pointer-events: none; 65 | transition: 0.1s; 66 | } 67 | 68 | .wrapper input::placeholder { 69 | color: rgb(157, 157, 157); 70 | opacity: 1; 71 | } 72 | 73 | .wrapper > label > span { 74 | width: 20px; 75 | height: 20px; 76 | padding: 4px; 77 | cursor: help; 78 | border-radius: 10px; 79 | box-sizing: border-box; 80 | font-size: 10px; 81 | vertical-align: middle; 82 | display: inline-block; 83 | position: relative; 84 | transform: translateY(-2px); 85 | -webkit-touch-callout: none; /* iOS Safari */ 86 | -webkit-user-select: none; /* Safari */ 87 | -khtml-user-select: none; /* Konqueror HTML */ 88 | -moz-user-select: none; /* Old versions of Firefox */ 89 | -ms-user-select: none; /* Internet Explorer/Edge */ 90 | user-select: none; /* Non-prefixed version, currently 91 | supported by Chrome, Edge, Opera and Firefox */ 92 | 93 | } 94 | 95 | .wrapper > label > span > span { 96 | width: 12px; 97 | height: 12px; 98 | display: inline-block; 99 | background: rgba(51, 124, 227, 0.769); 100 | border-radius: 6px; 101 | color: white; 102 | font-size: 10px; 103 | font-weight: bold; 104 | text-align: center; 105 | position: relative; 106 | outline: none; 107 | } 108 | 109 | .wrapper > label > span > span:after { 110 | opacity: 0; 111 | color: white; 112 | letter-spacing: 0; 113 | line-height: 1.375; 114 | transform: translateY(50%); 115 | box-shadow: 0 0 2px 0 #00000010, 116 | 0 4px 8px 0 #00000020; 117 | content: attr(aria-label); 118 | z-index: 10; 119 | left: 100%; 120 | bottom: 50%; 121 | min-height: 20px; 122 | position: absolute; 123 | padding: 0.25rem 0.75rem; 124 | background-color: #000000df; 125 | border-radius: 6px; 126 | font-family: Inter; 127 | font-size: 14px; 128 | max-width: 240px; 129 | font-weight: 500; 130 | transition: 0.15s all ease; 131 | width: max-content; 132 | height: min-content; 133 | right: 0; 134 | pointer-events: none; 135 | word-wrap: break-word; 136 | text-align: center; 137 | margin-left: 0.5rem; 138 | } 139 | 140 | .wrapper > label > span:hover > span:after, 141 | .wrapper > label > span:active > span:after, 142 | .wrapper > label > span:focus > span:after, 143 | .wrapper > label > span:focus-within > span:after { 144 | outline: none; 145 | z-index: 9; 146 | opacity: 1; 147 | } 148 | 149 | .wrapper > label > span:focus-within { 150 | background: rgba(51, 124, 227, 0.2); 151 | } -------------------------------------------------------------------------------- /components/Inputs/Title.js: -------------------------------------------------------------------------------- 1 | import { useState, uesEffet, useEffect } from 'react'; 2 | import styles from './Title.module.css'; 3 | 4 | function defaultValidate (text) { 5 | return text?.length; 6 | } 7 | 8 | export default function Title (props) { 9 | const { validate: _validate, name, description, help, placeholder, data, setData, initialData, wrapperClass, width, margin, type, id: _id, required } = props; 10 | 11 | const id = _id ?? name?.toLowerCase()?.split(' ')?.join('-')?.split('')?.filter(a => `abcdefghijklmnopqrstuvwxyz1234567890-_`.includes(a))?.join('') ?? 'error'; 12 | const validate = data => (_validate ?? defaultValidate)() && (required ? data?.length : true); 13 | 14 | const [localData, setLocalData] = useState(initialData ?? ''); 15 | const [valid, setValid] = useState(false); 16 | const [partiallyValid, setPartiallyValid] = useState(false); 17 | 18 | return ( 19 | <> 20 |
l).join(' ')} style={{ 21 | width: width ?? '300px', 22 | margin: margin ?? '0px', 23 | boxSizing: 'border-box' 24 | }}> 25 | 26 |

{description}

27 |
28 | 29 | ) 30 | } -------------------------------------------------------------------------------- /components/Inputs/Title.module.css: -------------------------------------------------------------------------------- 1 | .wrapper input { 2 | width: 100%; 3 | border-radius: 0.25em; 4 | border-color: #d3d3d4; 5 | transition: 0.2s; 6 | border-width: 2px; 7 | border-style: solid; 8 | padding: 0.5em; 9 | font-size: 1em; 10 | font-weight: 500; 11 | font-family: system-ui, Inter; 12 | } 13 | 14 | .wrapper input:hover { 15 | border-color: #b7b7b8; 16 | } 17 | 18 | .wrapper.isValid > span { 19 | filter: opacity(1); 20 | } 21 | 22 | .wrapper.isPartiallyValid > span { 23 | filter: opacity(0.6) grayscale(0.6); 24 | } 25 | 26 | .wrapper input[type="date"] + span { 27 | transform: translate(-26px, -1px); 28 | } 29 | 30 | .wrapper label { 31 | margin-bottom: 0.1em; 32 | font-weight: 600; 33 | cursor: text; 34 | } 35 | 36 | .wrapper p { 37 | margin-top: 0em; 38 | margin-bottom: 0.5em; 39 | color: #767676; 40 | font-size: 14px; 41 | font-weight: 500; 42 | } 43 | 44 | .wrapper input:focus { 45 | outline: none; 46 | border-color: var(--orange); 47 | box-shadow: 0 0 0 2px rgba(var(--orange-3-values), 0.2) 48 | } 49 | 50 | .wrapper { 51 | min-width: 200px; 52 | font-family: system-ui, Inter; 53 | position: relative; 54 | } 55 | 56 | .wrapper > span { 57 | color: #21ca71; 58 | font-weight: 700; 59 | font-size: 20px; 60 | position: absolute; 61 | display: inline-block; 62 | filter: opacity(0); 63 | right: 10px; 64 | bottom: 8px; 65 | pointer-events: none; 66 | transition: 0.1s; 67 | } 68 | 69 | .wrapper input::placeholder { 70 | color: rgb(157, 157, 157); 71 | opacity: 1; 72 | } 73 | 74 | .wrapper > label > span { 75 | width: 20px; 76 | height: 20px; 77 | padding: 4px; 78 | cursor: help; 79 | border-radius: 10px; 80 | box-sizing: border-box; 81 | font-size: 10px; 82 | vertical-align: middle; 83 | display: inline-block; 84 | position: relative; 85 | transform: translateY(-2px); 86 | -webkit-touch-callout: none; /* iOS Safari */ 87 | -webkit-user-select: none; /* Safari */ 88 | -khtml-user-select: none; /* Konqueror HTML */ 89 | -moz-user-select: none; /* Old versions of Firefox */ 90 | -ms-user-select: none; /* Internet Explorer/Edge */ 91 | user-select: none; /* Non-prefixed version, currently 92 | supported by Chrome, Edge, Opera and Firefox */ 93 | 94 | } 95 | 96 | .wrapper > label > span > span { 97 | width: 12px; 98 | height: 12px; 99 | display: inline-block; 100 | background: rgba(51, 124, 227, 0.769); 101 | border-radius: 6px; 102 | color: white; 103 | font-size: 10px; 104 | font-weight: bold; 105 | text-align: center; 106 | position: relative; 107 | outline: none; 108 | } 109 | 110 | .wrapper > label > span > span:after { 111 | opacity: 0; 112 | color: white; 113 | letter-spacing: 0; 114 | line-height: 1.375; 115 | transform: translateY(50%); 116 | box-shadow: 0 0 2px 0 #00000010, 117 | 0 4px 8px 0 #00000020; 118 | content: attr(aria-label); 119 | z-index: 10; 120 | left: 100%; 121 | bottom: 50%; 122 | min-height: 20px; 123 | position: absolute; 124 | padding: 0.25rem 0.75rem; 125 | background-color: #000000df; 126 | border-radius: 6px; 127 | font-family: Inter; 128 | font-size: 14px; 129 | max-width: 240px; 130 | font-weight: 500; 131 | transition: 0.15s all ease; 132 | width: max-content; 133 | height: min-content; 134 | right: 0; 135 | pointer-events: none; 136 | word-wrap: break-word; 137 | text-align: center; 138 | margin-left: 0.5rem; 139 | } 140 | 141 | .wrapper > label > span:hover > span:after, 142 | .wrapper > label > span:active > span:after, 143 | .wrapper > label > span:focus > span:after, 144 | .wrapper > label > span:focus-within > span:after { 145 | outline: none; 146 | z-index: 9; 147 | opacity: 1; 148 | } 149 | 150 | .wrapper > label > span:focus-within { 151 | background: rgba(51, 124, 227, 0.2); 152 | } -------------------------------------------------------------------------------- /components/Modal.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export default function Modal ({ visible, setVisible, children, hideCloseButton }) { 4 | if (visible) return ( 5 | <> 6 |
{ 16 | setVisible(false); 17 | }}> 18 | 19 |
20 |
34 | {!hideCloseButton && 35 |
{ 52 | setVisible(false); 53 | }}> 54 | × 55 |
56 | } 57 | {children} 58 |
59 | 60 | ); 61 | return ( <> ) 62 | } -------------------------------------------------------------------------------- /components/Sidebar.js: -------------------------------------------------------------------------------- 1 | import Icon from "@hackclub/icons"; 2 | import styles from "../styles/Home.module.css"; 3 | import { useEffect, useState } from "react"; 4 | 5 | 6 | export default function Sidebar () { 7 | const [donor, setDonor] = useState(''); 8 | useEffect(() => { 9 | fetch('/api/donor').then(res => res.text()).then(setDonor); 10 | if (!localStorage.getItem('hackoc-analytics')) { 11 | fetch('https://ip.yodacode.xyz').then(res => res.json()).then(({ geo }) => { 12 | splitbee.user.set({ 13 | city: geo.city 14 | }); 15 | localStorage.setItem('hackoc-analytics', true); 16 | }); 17 | } 18 | }, []); 19 | return ( 20 |
23 | 24 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |

Sponsored by

53 | 54 | 55 | 56 | {/*

Hack OC wouldn't be possible without help from our sponsors. Want to help make Hack OC incredible? Email us at team@hackoc.org or check out our { 57 | e.preventDefault(); 58 | splitbee.track("Prospectus Download", { 59 | ...(email ? { email } : {}) 60 | }); 61 | window.open('/prospectus'); 62 | }}>prospectus to get involved!

*/} 63 |
64 |

SPECIAL THANKS TO

67 |
74 | 78 | 79 | 80 | 81 | 82 | 83 | 86 |
95 |
96 |

{donor}

103 |

and other generous donors 110 | 113 |

114 |
115 |
116 |
117 |
118 |
119 | ); 120 | } -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 404: This page could not be found

404

This page could not be found.

-------------------------------------------------------------------------------- /docs/_next/static/63naq6OZNUwEmbBG38Fkb/_buildManifest.js: -------------------------------------------------------------------------------- 1 | self.__BUILD_MANIFEST={__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":["static/chunks/7bad3855-0bb9e0fb6954a08c.js","static/css/5599a730af22f4f4.css","static/chunks/pages/index-c7c2ac66f3a267fe.js"],"/_error":["static/chunks/pages/_error-7397496ca01950b1.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB() -------------------------------------------------------------------------------- /docs/_next/static/63naq6OZNUwEmbBG38Fkb/_ssgManifest.js: -------------------------------------------------------------------------------- 1 | self.__SSG_MANIFEST=new Set,self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB() -------------------------------------------------------------------------------- /docs/_next/static/chunks/pages/_app-fd9d1b23c3421e47.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{1118:function(a,b,c){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return c(2373)}])},2373:function(a,b,c){"use strict";function d(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}c.r(b),c.d(b,{default:function(){return f}});var e=c(5893);c(906);var f=function(a){var b=a.Component,c=a.pageProps;return(0,e.jsxs)(e.Fragment,{children:[(0,e.jsx)("link",{rel:"preconnect",href:"https://fonts.googleapis.com"}),(0,e.jsx)("link",{rel:"preconnect",href:"https://fonts.gstatic.com",crossOrigin:"true"}),(0,e.jsx)("link",{href:"https://fonts.googleapis.com/css2?family=Readex+Pro:wght@200;300;400;500;600;700&display=swap",rel:"stylesheet"}),(0,e.jsx)(b,function(a){for(var b=1;b0&&a[f-1][2]>e;f--)a[f]=a[f-1];a[f]=[c,d,e];return}for(var h=1/0,f=0;f=e&&Object.keys(g.O).every(function(a){return g.O[a](c[j])})?c.splice(j--,1):(i=!1,eHack OC

Hack OC

Orange County's First Post-Pandemic Hackathon

What's Hack OC?

Hack OC is the first in-person high school hackathon after the pandemic in Orange County, California. We're inviting you and anyone else to participate in 10 hours of coding, building, learning, and sharing.


What's a "hackathon"?

Hackathons are in-person coding events where teenagers come together to learn new skills, create fun projects, and make memories, typically for a day or two. There's also food, snacks, and drinks to fuel your creativity.

-------------------------------------------------------------------------------- /docs/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackoc/site/5283c43eb665bc49ca91d91ff42867f81e035ba0/docs/orange.png -------------------------------------------------------------------------------- /docs/orange.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /docs/white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /lib/questions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Full Name ✅ 3 | * Ponouns ✅ 4 | * Technical Skill Level ✅ 5 | * Dietery Restrictions ✅ 6 | * Parent/Guardian Name ✅ 7 | * Parent/Guardian Email ✅ 8 | * Email ✅ 9 | * Shirt Size ✅ 10 | * Date of Birth + Grade ✅ 11 | * Vaccination Status ✅ 12 | * Tabs or Spaces 13 | * Pineapple on Pizza 14 | */ 15 | 16 | import caSchools from './caSchools.js'; 17 | const schoolsList = caSchools.map(school => school.city ? `${school.school} - ${school.city}` : school.school ?? ''); 18 | 19 | export const questions = { 20 | fullName: { 21 | name: 'Full Name', 22 | type: 'text', 23 | required: true, 24 | placeholder: 'Jane Doe', 25 | description: '', 26 | help: '', 27 | verify: () => true 28 | }, 29 | email: { 30 | name: 'Email', 31 | type: 'email', 32 | required: true, 33 | placeholder: 'jane@example.com', 34 | description: 'We\'ll reach out to you via email for your vaccination status, event waivers, and more.', 35 | help: '', 36 | verify: email => /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(email) 37 | }, 38 | pronouns: { 39 | name: 'Pronouns', 40 | type: 'text', 41 | required: false, 42 | placeholder: 'they/them', 43 | description: '', 44 | help: '', 45 | verify: () => true 46 | }, 47 | parentName: { 48 | name: 'Parent Name', 49 | type: 'text', 50 | required: true, 51 | placeholder: 'John Doe', 52 | description: '', 53 | help: '', 54 | verify: () => true 55 | }, 56 | parentEmail: { 57 | name: 'Parent Email', 58 | type: 'email', 59 | required: true, 60 | placeholder: 'john@example.com', 61 | description: '', 62 | help: '', 63 | verify: email => /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(email) 64 | }, 65 | dateOfBirth: { 66 | name: 'Birthday', 67 | type: 'date', 68 | required: true, 69 | description: 'All high school & upper-middle-school aged students are welcome to come!', 70 | help: '', 71 | verify: () => true 72 | }, 73 | shirtSize: { 74 | name: 'T-Shirt Size', 75 | special: 'select', 76 | options: [ 77 | 'Extra Small', 78 | 'Small', 79 | 'Medium', 80 | 'Large', 81 | 'Extra Large' 82 | ], 83 | required: true, 84 | description: 'You won\'t want to miss our awesome swag!', 85 | help: '', 86 | verify: () => true 87 | }, 88 | skillLevel: { 89 | name: 'Skill Level', 90 | special: 'select', 91 | options: [ 92 | 'Beginner: I have never coded before or just started learning', 93 | 'Intermediate: I have taken CS classes OR worked on small individual projects', 94 | 'Advanced: I\'m comfortable with my skill set and can work on a project without much guidance' 95 | ], 96 | required: true, 97 | description: 'How would you describe your technical skills? Everyone\'s welcome at the event, but this helps us gauge what resources we need to provide.', 98 | help: '', 99 | verify: () => true 100 | }, 101 | dietaryRestrictions: { 102 | name: 'Dietary Restrictions', 103 | special: 'multiSelect', 104 | options: [ 105 | 'Vegetarian', 106 | 'Vegan', 107 | 'Kosher', 108 | 'Halal', 109 | 'Dairy-free', 110 | 'Gluten-free', 111 | 'Nut allergy', 112 | { custom: true } 113 | ], 114 | dontDisplayAll: false, 115 | placeholder: 'Select or add...', 116 | required: false, 117 | description: 'Leave blank if none.', 118 | help: '', 119 | verify: () => true 120 | }, 121 | school: { 122 | name: 'School', 123 | special: 'select', 124 | options: [ ...schoolsList, { custom: true } ], 125 | dontDisplayAll: true, 126 | required: true, 127 | description: '', 128 | help: '', 129 | verify: () => true 130 | }, 131 | referrer: { 132 | name: 'Referrer', 133 | type: 'text', 134 | required: false, 135 | description: 'If someone told you about Hack OC, put their name here!', 136 | help: '', 137 | verify: () => true 138 | }, 139 | // vaccinationNote: { 140 | // name: 'Full Vaccination Required', 141 | // special: 'text', 142 | // description: `Hack OC requires all attendees to be fully vaccinated. By submitting this registration form, you are confirming that you have received two (2) doses of the Pfizer-BioNTech or Moderna COVID-19 immunization, or one (1) dose of the J&J/Janssen COVID-19 immunization. We will be in touch with you to verify by requiring a photo of your physical vaccine card or a SMART digital vaccine card.` 143 | // } 144 | } 145 | 146 | export const sections = [ 147 | { 148 | title: 'Basic Information', 149 | description: 'Let\'s start off with the basics', 150 | questions: [ 151 | questions.fullName, 152 | questions.email, 153 | questions.dateOfBirth, 154 | questions.pronouns 155 | ] 156 | }, 157 | { 158 | title: 'Parent/Guardian Contact', 159 | description: 'Contact infromation for your parent/guardian', 160 | questions: [ 161 | questions.parentName, 162 | questions.parentEmail 163 | ] 164 | }, 165 | { 166 | title: 'Your Profile', 167 | description: 'Let\'s get to know you a bit better', 168 | questions: [ 169 | questions.shirtSize, 170 | questions.skillLevel, 171 | questions.dietaryRestrictions, 172 | questions.school, 173 | questions.referrer 174 | ] 175 | } 176 | ]; 177 | 178 | export default { questions, sections }; -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const campaigns = { 4 | capo: 'https://hackoc.org/?utm_source=poster&utm_medium=physical&utm_campaign=capo&utm_content=qr', 5 | ihs: 'https://hackoc.org/?utm_source=poster&utm_medium=physical&utm_campaign=ihs&utm_content=qr', 6 | test: 'https://example.com/?utm_source=poster&utm_medium=physical&utm_campaign=test&utm_content=qr' 7 | }; 8 | 9 | const releases = { 10 | '2023-03-13': '1.pdf' 11 | }; 12 | 13 | const nextConfig = { 14 | reactStrictMode: true, 15 | swcMinify: true, 16 | rewrites: async () => { 17 | return [ 18 | { 19 | source: "/bee.js", 20 | destination: "https://cdn.splitbee.io/sb.js", 21 | 22 | }, 23 | { 24 | source: "/_hive/:slug", 25 | destination: "https://hive.splitbee.io/:slug", 26 | }, 27 | { 28 | source: '/registration/link-discord/callback', 29 | destination: '/api/discord/link', 30 | }, 31 | { 32 | source: '/s/:id', 33 | destination: '/scrapbook', 34 | }, 35 | { 36 | source: '/h/:id', 37 | destination: '/ships', 38 | }, 39 | ...Object.entries(releases).map(([ name, file ]) => ({ 40 | source: `/press/releases/${name}`, 41 | destination: `/press/${file}`, 42 | })) 43 | ]; 44 | }, 45 | redirects: async () => { 46 | return [ 47 | { 48 | source: '/discord', 49 | destination: 'https://discord.gg/cPfHW8SQ5B', 50 | basePath: false, 51 | permanent: false 52 | }, 53 | { 54 | source: '/lastchance', 55 | destination: 'https://hackoc.org/?utm_source=twitter&utm_campaign=lastchance&utm_medium=link&utm_content=lastchance', 56 | basePath: false, 57 | permanent: false 58 | }, 59 | { 60 | source: '/event/1', 61 | destination: 'https://hackoc.org/discord', 62 | basePath: false, 63 | permanent: false 64 | }, 65 | { 66 | source: '/branding', 67 | destination: 'https://www.figma.com/file/R1sQCpxFQqQ8laUmySwoZ3/Hack-OC-Branding-%7C-Public-Facing?node-id=0%3A1&t=sCjA6w0V7Q2b639x-1', 68 | basePath: false, 69 | permanent: false 70 | }, 71 | { 72 | source: '/gh/:repo', 73 | destination: 'https://github.com/hackoc/:repo', 74 | basePath: false, 75 | permanent: false 76 | }, 77 | { 78 | source: '/github/:repo', 79 | destination: 'https://github.com/hackoc/:repo', 80 | basePath: false, 81 | permanent: false 82 | }, 83 | { 84 | source: '/finances', 85 | destination: 'https://bank.hackclub.com/hackoc', 86 | basePath: false, 87 | permanent: false 88 | }, 89 | { 90 | source: '/bank', 91 | destination: 'https://hackoc.org/finances', 92 | basePath: false, 93 | permanent: false 94 | }, 95 | { 96 | source: '/money', 97 | destination: 'https://hackoc.org/finances', 98 | basePath: false, 99 | permanent: false 100 | }, 101 | { 102 | source: '/github', 103 | destination: 'https://github.com/hackoc', 104 | basePath: false, 105 | permanent: false 106 | }, 107 | { 108 | source: '/donate', 109 | destination: 'https://bank.hackclub.com/donations/start/hackoc', 110 | basePath: false, 111 | permanent: false 112 | }, 113 | { 114 | source: '/contact', 115 | destination: 'mailto:team@hackoc.org', 116 | basePath: false, 117 | permanent: false 118 | }, 119 | { 120 | source: '/prospectus', 121 | destination: 'https://hackoc.org/prospectus.pdf', 122 | basePath: false, 123 | permanent: false 124 | }, 125 | { 126 | source: '/sponsorship-prospectus', 127 | destination: 'https://hackoc.org/prospectus.pdf', 128 | basePath: false, 129 | permanent: false 130 | }, 131 | { 132 | source: '/swag', 133 | destination: "https://forms.gle/78DevocjpseXDSeFA", 134 | basePath: false, 135 | permanent: false 136 | }, 137 | { 138 | source: '/email', 139 | destination: 'mailto:team@hackoc.org', 140 | basePath: false, 141 | permanent: false 142 | }, 143 | { 144 | source: '/vote', 145 | destination: 'https://forms.gle/mM6ezbSRbWGJhfxb9', 146 | basePath: false, 147 | permanent: false 148 | }, 149 | { 150 | source: '/flyer1', 151 | destination: 'https://drive.google.com/file/d/1VOOkyHptTRwy-sA1JrVfYu-Vu4Y0MEyw/view', 152 | basePath: false, 153 | permanent: false 154 | }, 155 | ...Object.entries(campaigns).map(([campaign, url]) => ({ 156 | source: `/c/${campaign}`, 157 | destination: url, 158 | basePath: false, 159 | permanent: false 160 | })) 161 | ] 162 | } 163 | } 164 | 165 | module.exports = nextConfig 166 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hackoc", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages-build": "next build && next export && rm -rf docs && mv out docs" 11 | }, 12 | "dependencies": { 13 | "@fillout/react": "^3.0.0", 14 | "@hackclub/icons": "^0.0.9", 15 | "@splitbee/web": "^0.3.0", 16 | "@vercel/analytics": "^0.1.11", 17 | "mongodb": "^4.13.0", 18 | "next": "12.2.5", 19 | "node-fetch": "^3.3.0", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "react-google-recaptcha": "^2.1.0", 23 | "react-masonry-css": "^1.0.16", 24 | "short-unique-id": "^4.4.4" 25 | }, 26 | "devDependencies": { 27 | "eslint": "8.22.0", 28 | "eslint-config-next": "12.2.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import Head from 'next/head' 3 | import Script from 'next/script' 4 | import { useEffect } from 'react' 5 | import splitbee from '@splitbee/web'; 6 | import { Analytics } from "@vercel/analytics/react"; 7 | 8 | 9 | function MyApp({ Component, pageProps }) { 10 | useEffect(() => { 11 | window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) } 12 | splitbee.init({ 13 | scriptUrl: "/bee.js", 14 | apiUrl: "/_hive", 15 | }); 16 | }, []); 17 | return ( 18 | <> 19 | 20 | {/* dangerous innerhtml script */} 21 | 26 | 24 | 25 | 26 | `); 27 | } -------------------------------------------------------------------------------- /pages/api/donor.js: -------------------------------------------------------------------------------- 1 | export default async function (req, res) { 2 | const response = await fetch('https://bank.hackclub.com/api/v3/organizations/hackoc/donations'); 3 | const json = await response.json(); 4 | res.send( 5 | json.filter(a =>a?.memo?.includes?.("Donation from")) 6 | .sort(() => Math.random() - 0.5)[0]?.donor?.name 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /pages/api/email/[city]/[email].js: -------------------------------------------------------------------------------- 1 | export default async function (req, res) { 2 | await fetch(process.env.SLACK_WEBHOOK, { 3 | method: 'POST', 4 | headers: { 5 | 'Content-Type': 'application/json' 6 | }, 7 | body: JSON.stringify({ 8 | email: req.query.email, 9 | ip_city: req.query.city 10 | }) 11 | }); 12 | res.send('OK') 13 | } -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /pages/api/posts.js: -------------------------------------------------------------------------------- 1 | import ShortUniqueId from "short-unique-id"; 2 | import fetch from "node-fetch"; 3 | 4 | const sleep = () => new Promise((resolve) => { 5 | setTimeout(() => { 6 | resolve(); 7 | }, 350); 8 | }); 9 | 10 | export function dbConnect () { 11 | return new Promise((resolve, reject) => { 12 | const { MongoClient, ServerApiVersion } = require('mongodb'); 13 | const uri = process.env.MONGODB_URI; 14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); 15 | client.connect(err => { 16 | if (err) return reject(err); 17 | resolve(client); 18 | // perform actions on the collection object 19 | }); 20 | }); 21 | 22 | } 23 | 24 | 25 | export default async function handler(req, res) { 26 | const client = await dbConnect(); 27 | 28 | const collection = client.db("primary").collection("scrapbook"); 29 | 30 | const posts = (await collection.find().toArray()); 31 | const registrations = (await client.db("primary").collection("users").find().toArray()); 32 | const users = {}; 33 | registrations.forEach(registration => { 34 | users[registration['Discord Tag']] = registration['Full Name'] 35 | }); 36 | res.json(posts.map(post => { 37 | if (users[post.name]) post.name = users[post.name]; 38 | return post; 39 | })); 40 | client.close(); 41 | } 42 | -------------------------------------------------------------------------------- /pages/api/register.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | 3 | const sleep = () => new Promise((resolve) => { 4 | setTimeout(() => { 5 | resolve(); 6 | }, 350); 7 | }); 8 | 9 | export function dbConnect () { 10 | return new Promise((resolve, reject) => { 11 | const { MongoClient, ServerApiVersion } = require('mongodb'); 12 | const uri = process.env.MONGODB_URI; 13 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); 14 | client.connect(err => { 15 | if (err) return reject(err); 16 | resolve(client); 17 | // perform actions on the collection object 18 | }); 19 | }); 20 | 21 | } 22 | 23 | async function email (email, name) { 24 | const token = `Bearer ${process.env.MAIL_KEY}`; 25 | const res = await fetch('https://api.hackoc.org/mail/v1/authed/deliver/register', { 26 | method: 'POST', 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | 'Authorization': token 30 | }, 31 | body: JSON.stringify({ 32 | data: { 33 | firstName: name.split(' ')[0], 34 | email 35 | } 36 | }) 37 | }); 38 | const json = await res.json(); 39 | console.log(json); 40 | return json; 41 | } 42 | 43 | 44 | export default async function handler(req, res) { 45 | const dbPromise = dbConnect(); 46 | const { body, method } = req; 47 | 48 | // Extract the email and captcha code from the request body 49 | const { data, captcha } = body; 50 | 51 | if (method === "POST") { 52 | // If email or captcha are missing return an error 53 | if (!captcha || !data) { 54 | return res.status(422).json({ 55 | message: "Unproccesable request, please provide the required fields", 56 | }); 57 | } 58 | 59 | try { 60 | // Ping the google recaptcha verify API to verify the captcha code you received 61 | const response = await fetch( 62 | `https://www.google.com/recaptcha/api/siteverify?secret=${process.env.RECAPTCHA_SECRET_KEY}&response=${captcha}`, 63 | { 64 | headers: { 65 | "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", 66 | }, 67 | method: "POST", 68 | } 69 | ); 70 | const captchaValidation = await response.json(); 71 | /** 72 | * The structure of response from the veirfy API is 73 | * { 74 | * "success": true|false, 75 | * "challenge_ts": timestamp, // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ) 76 | * "hostname": string, // the hostname of the site where the reCAPTCHA was solved 77 | * "error-codes": [...] // optional 78 | } 79 | */ 80 | if (captchaValidation.success) { 81 | // Replace this with the API that will save the data received 82 | // to your backend 83 | const client = await dbPromise; 84 | const collection = client.db("primary").collection("users"); 85 | const existingRecord = (await collection.findOne({ 86 | Email: data["Email"] 87 | })); 88 | if (existingRecord) return res.status(422).json({ message: "This email has already registered for Hack OC." }); 89 | console.log(await collection.insertOne(data)); 90 | client.close(); 91 | // Return 200 if everything is successful 92 | try { 93 | await email(data["Email"], data["Full Name"]); 94 | await fetch(process.env.LATE_WEBHOOK, { 95 | method: 'POST', 96 | body: JSON.stringify({ 97 | Email: data["Email"], 98 | Name: data["Full Name"], 99 | }) 100 | }); 101 | } catch (err) { 102 | 103 | return res.status(422).json({ message: "We had trouble sending you an email. Please report this error." }); 104 | } 105 | return res.status(200).send("OK"); 106 | } 107 | 108 | return res.status(422).json({ 109 | message: "Unproccesable request, Invalid captcha code", 110 | }); 111 | } catch (error) { 112 | console.log(error); 113 | return res.status(422).json({ message: "Something went wrong" }); 114 | } 115 | } 116 | // Return 404 if someone pings the API with a method other than 117 | // POST 118 | return res.status(404).send("Invalid method. please use POST"); 119 | } 120 | -------------------------------------------------------------------------------- /pages/api/scanner.js: -------------------------------------------------------------------------------- 1 | import { dbConnect } from "./register"; 2 | 3 | export default async function scanner (req, res) { 4 | const { data } = req.query; 5 | 6 | const dbPromise = dbConnect(); 7 | 8 | const client = await dbPromise; 9 | const collection = client.db("primary").collection("users"); 10 | const existingRecord = (await collection.findOne({ 11 | Email: data["Email"] 12 | })); 13 | if (existingRecord) return res.status(422).json({ message: "This email has already registered for Hack OC." }); 14 | console.log(await collection.insertOne(data)); 15 | client.close(); 16 | 17 | 18 | res.send("OK"); 19 | } -------------------------------------------------------------------------------- /pages/api/scrapbookdelete.js: -------------------------------------------------------------------------------- 1 | import ShortUniqueId from "short-unique-id"; 2 | import fetch from "node-fetch"; 3 | 4 | const sleep = () => new Promise((resolve) => { 5 | setTimeout(() => { 6 | resolve(); 7 | }, 350); 8 | }); 9 | 10 | export function dbConnect () { 11 | return new Promise((resolve, reject) => { 12 | const { MongoClient, ServerApiVersion } = require('mongodb'); 13 | const uri = process.env.MONGODB_URI; 14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); 15 | client.connect(err => { 16 | if (err) return reject(err); 17 | resolve(client); 18 | // perform actions on the collection object 19 | }); 20 | }); 21 | 22 | } 23 | 24 | 25 | export default async function handler(req, res) { 26 | if (req.body.token !== process.env.PRIVATE_TOKEN) return res.send("oops"); 27 | const client = await dbConnect(); 28 | 29 | 30 | 31 | const collection = client.db("primary").collection("scrapbook"); 32 | 33 | const query = req.body.id ? { 34 | shortId: req.body.id 35 | 36 | } : { 37 | discord: req.body.discord 38 | } 39 | 40 | const response = (await collection.deleteOne(query)); 41 | res.json({ 42 | ok: true 43 | }); 44 | client.close(); 45 | } 46 | -------------------------------------------------------------------------------- /pages/api/scrapbookpost.js: -------------------------------------------------------------------------------- 1 | import ShortUniqueId from "short-unique-id"; 2 | import fetch from "node-fetch"; 3 | 4 | const sleep = () => new Promise((resolve) => { 5 | setTimeout(() => { 6 | resolve(); 7 | }, 350); 8 | }); 9 | 10 | export function dbConnect () { 11 | return new Promise((resolve, reject) => { 12 | const { MongoClient, ServerApiVersion } = require('mongodb'); 13 | const uri = process.env.MONGODB_URI; 14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); 15 | client.connect(err => { 16 | if (err) return reject(err); 17 | resolve(client); 18 | // perform actions on the collection object 19 | }); 20 | }); 21 | 22 | } 23 | 24 | const uid = new ShortUniqueId({ length: 10 }); 25 | 26 | export default async function handler(req, res) { 27 | if (req.body.token !== process.env.PRIVATE_TOKEN) return res.send("oops"); 28 | const client = await dbConnect(); 29 | const shortId = uid(); 30 | 31 | 32 | 33 | const collection = client.db("primary").collection("scrapbook"); 34 | 35 | const response = (await collection.insertOne({ 36 | name: req.body.name, 37 | avatar: req.body.avatar, 38 | user: req.body.user, 39 | message: req.body.message, 40 | image: req.body.image, 41 | shortId, 42 | discord: req.body.discord 43 | 44 | })); 45 | res.json({ 46 | mongoDbId: response.insertedId, 47 | shortId 48 | }); 49 | client.close(); 50 | } 51 | -------------------------------------------------------------------------------- /pages/api/shippost.js: -------------------------------------------------------------------------------- 1 | import ShortUniqueId from "short-unique-id"; 2 | import fetch from "node-fetch"; 3 | 4 | const sleep = () => new Promise((resolve) => { 5 | setTimeout(() => { 6 | resolve(); 7 | }, 350); 8 | }); 9 | 10 | export function dbConnect () { 11 | return new Promise((resolve, reject) => { 12 | const { MongoClient, ServerApiVersion } = require('mongodb'); 13 | const uri = process.env.MONGODB_URI; 14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); 15 | client.connect(err => { 16 | if (err) return reject(err); 17 | resolve(client); 18 | // perform actions on the collection object 19 | }); 20 | }); 21 | 22 | } 23 | 24 | const uid = new ShortUniqueId({ length: 10 }); 25 | 26 | export default async function handler(req, res) { 27 | if (req.body.token !== process.env.PRIVATE_TOKEN) return res.send("oops"); 28 | const client = await dbConnect(); 29 | const shortId = uid(); 30 | 31 | 32 | 33 | const collection = client.db("primary").collection("ship"); 34 | 35 | const response = (await collection.insertOne({ 36 | name: req.body.name, 37 | avatar: req.body.avatar, 38 | user: req.body.user, 39 | message: req.body.message, 40 | image: req.body.image, 41 | shortId, 42 | title: req.body.title, 43 | discord: req.body.discord 44 | 45 | })); 46 | res.json({ 47 | mongoDbId: response.insertedId, 48 | shortId 49 | }); 50 | client.close(); 51 | } 52 | -------------------------------------------------------------------------------- /pages/api/ships.js: -------------------------------------------------------------------------------- 1 | import ShortUniqueId from "short-unique-id"; 2 | import fetch from "node-fetch"; 3 | 4 | const sleep = () => new Promise((resolve) => { 5 | setTimeout(() => { 6 | resolve(); 7 | }, 350); 8 | }); 9 | 10 | export function dbConnect () { 11 | return new Promise((resolve, reject) => { 12 | const { MongoClient, ServerApiVersion } = require('mongodb'); 13 | const uri = process.env.MONGODB_URI; 14 | const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 }); 15 | client.connect(err => { 16 | if (err) return reject(err); 17 | resolve(client); 18 | // perform actions on the collection object 19 | }); 20 | }); 21 | 22 | } 23 | 24 | 25 | export default async function handler(req, res) { 26 | const client = await dbConnect(); 27 | 28 | const collection = client.db("primary").collection("ship"); 29 | 30 | const posts = (await collection.find().toArray()); 31 | 32 | console.log(posts); 33 | const registrations = (await client.db("primary").collection("users").find().toArray()); 34 | const users = {}; 35 | registrations.forEach(registration => { 36 | users[registration['Discord Tag']] = registration['Full Name'] 37 | }); 38 | res.json(posts.map(post => { 39 | if (users[post.name]) post.name = users[post.name]; 40 | return post; 41 | })); 42 | client.close(); 43 | } 44 | -------------------------------------------------------------------------------- /pages/api/v2.js: -------------------------------------------------------------------------------- 1 | export default async function (req, res) { 2 | const { email, city } = req.body; 3 | const response = await fetch('https://api.airtable.com/v0/appYlvRWZObGXXGOh/Emails', { 4 | method: 'POST', 5 | headers: { 6 | Authorization: `Bearer ${process.env.AIRTABLE_KEY}`, 7 | 'Content-Type': 'application/json' 8 | }, 9 | body: JSON.stringify({ 10 | records: [ 11 | { 12 | fields: { 13 | Email: email, 14 | City: city 15 | } 16 | } 17 | ] 18 | }) 19 | }); 20 | const data = await response.json(); 21 | console.log(data); 22 | res.send('OK'); 23 | } -------------------------------------------------------------------------------- /pages/api/webhook.js: -------------------------------------------------------------------------------- 1 | export default async function (req, res) { 2 | const response = await fetch('http://mail2.hackoc.org/v1/webhook').then(response => response.text()); 3 | res.send(response); 4 | } -------------------------------------------------------------------------------- /pages/files/[file].js: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | 3 | export function Header ({ fileName }) { 4 | return ( 5 |
11 |
13 | 14 |
15 |
18 |
22 |

{fileName}

28 |
29 |
30 | ) 31 | } 32 | 33 | export default function FileViewer () { 34 | 35 | const router = useRouter(); 36 | const { file } = router.query; 37 | 38 | return ( 39 | <> 40 |
41 | 46 | 47 | ) 48 | 49 | 50 | } -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import styles from '../styles/Home.module.css' 3 | import Icon from '@hackclub/icons' 4 | import Modal from '../components/Modal' 5 | import { useEffect, useState } from 'react'; 6 | import splitbee from '@splitbee/web'; 7 | import with$, { withNodrag } from '../utils/cssUtils'; 8 | import useMedia, { useViewport } from '../utils/useMedia'; 9 | import Sidebar from '../components/Sidebar'; 10 | import { FilloutStandardEmbed } from "@fillout/react"; 11 | 12 | const meta_desc = "Orange County's high school hackathon"; 13 | const theme_color = '#FA7B33'; 14 | const social_image = 'https://hc-cdn.hel1.your-objectstorage.com/s/v3/777fe734e848f3b800c7ea600764edf9ddd5c2ac_open-graph.png'; 15 | 16 | const regex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; 17 | 18 | // const timelapseId = "9x00RCb1N7WTpAl6cIN0000Kult00vyzslROW6A1RblWwxM" 19 | 20 | const timelapseId = "402YMZJfp6kW02302E3r1RMe013Ub9AqlPwzr4VjD00HO7ME" 21 | 22 | export function BaseCard ({ children, style }) { 23 | return ( 24 |
30 | {children} 31 |
32 | ) 33 | } 34 | 35 | export function ImageCard ({ style, src, alt, caption, left }) { 36 | return ( 37 | 42 | {alt} 49 | {caption &&

{caption}

} 57 |
58 | ) 59 | } 60 | 61 | export function Card ({ title, icon, children, style }) { 62 | return ( 63 | 64 | 65 | 73 | 74 | 75 |

{title}

80 | 83 | 84 |
85 |

{children}

88 | 89 |
90 | ) 91 | } 92 | 93 | export default function Home({ formParams }) { 94 | const [modal, setModal] = useState(false); 95 | const handleFormEnter = () => { 96 | if (regex.test(email) && !loading) { 97 | return window.location.href = '/register?email=' + encodeURIComponent(email); 98 | 99 | setLoading(true); 100 | fetch('https://ip.yodacode.xyz').then(res => res.json()).then(({ geo }) => { 101 | splitbee.track("Email Subscribe", { 102 | email, 103 | city: geo.city 104 | }); 105 | fetch('/api/v2', { 106 | method: 'POST', 107 | headers: { 108 | 'Content-Type': 'application/json' 109 | }, 110 | body: JSON.stringify({ 111 | email, 112 | city: geo.city 113 | }) 114 | }).then(() => { 115 | fetch('/api/webhook').then(() => { 116 | setSubmitted(true); 117 | }); 118 | }); 119 | }) 120 | } 121 | }; 122 | const [email, setEmail] = useState(''); 123 | const [submitted, setSubmitted] = useState(false); 124 | const [loading, setLoading] = useState(false); 125 | 126 | const { width, height } = useMedia(); 127 | 128 | const cardSize = '380px'; 129 | const cardGap = '20px'; 130 | 131 | return ( 132 | <> 133 | 134 | 147 | 148 | 149 | Hack OC 2025 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |
178 |
188 | 191 |

Coming Soon

194 | 195 |
196 |
197 | 198 | ) 199 | } 200 | 201 | export function getServerSideProps(context) { 202 | const { req } = context; 203 | const { headers } = req; 204 | 205 | return { 206 | props: { 207 | formParams: { 208 | est_lat: headers['x-vercel-ip-latitude'], 209 | est_long: headers['x-vercel-ip-longitude'], 210 | est_city: decodeURIComponent(headers['x-vercel-ip-city']) 211 | } 212 | }, 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /pages/register-old.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import styles from '../styles/Register.module.css' 3 | import Icon from '@hackclub/icons' 4 | import Modal from '../components/Modal' 5 | import Text from '../components/Inputs/Text.js'; 6 | import Title from '../components/Inputs/Title.js'; 7 | import Select from '../components/Inputs/Select.js'; 8 | import { questions, sections } from '../lib/questions.js'; 9 | import { createRef, useEffect, useState } from 'react'; 10 | 11 | const meta_desc = "Orange County's first high school coding event since the pandemic. Join us for 12 hours of hacking, workshops, & friendship."; 12 | const theme_color = '#FA7B33'; 13 | const social_image = '/social.png'; 14 | 15 | import ReCAPTCHA from "react-google-recaptcha"; 16 | import useProtection from '../utils/useProtection'; 17 | import splitbee from '@splitbee/web'; 18 | 19 | const regex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; 20 | 21 | const timelapseId = "9x00RCb1N7WTpAl6cIN0000Kult00vyzslROW6A1RblWwxM" 22 | 23 | // const timelapseId = "402YMZJfp6kW02302E3r1RMe013Ub9AqlPwzr4VjD00HO7ME" 24 | 25 | export default function Register() { 26 | 27 | const [modal, setModal] = useState(false); 28 | const recaptchaRef = createRef(); 29 | 30 | const [formData, setFormData] = useState({}); 31 | 32 | function setValue (name, value) { 33 | let newFormData = JSON.parse(JSON.stringify(formData)) 34 | newFormData[name] = value; 35 | setFormData(newFormData); 36 | } 37 | 38 | 39 | function validate () { 40 | const v = Object.values(questions); 41 | let valid = true; 42 | let missing = []; 43 | for (let i = 0; i < v.length; i++) { 44 | const { required, verify, name } = v[i]; 45 | const value = formData[name]; 46 | const isValid = ( 47 | (required ? (value instanceof Array ? value.length : value) : true) 48 | && 49 | (verify?.(value)) 50 | ); 51 | console.log(value, name, isValid) 52 | if (!isValid && v[i].special !== 'text') { 53 | valid = false; 54 | missing.push(name) 55 | } 56 | } 57 | return {valid,missing}; 58 | } 59 | 60 | const [valid, setValid] = useState(validate()); 61 | 62 | useEffect(() => { 63 | setValid(validate()); 64 | }, [formData]); 65 | 66 | const [key, setKey] = useState(''); 67 | const [showForm, setShowForm] = useState(true); 68 | const [captchaCode, setCaptchaCode] = useState(null); 69 | const [discordTag, setDiscordTag] = useState(''); 70 | const [discordId, setDiscordId] = useState(''); 71 | 72 | useEffect(() => { 73 | const interval = setInterval(() => { 74 | const tag = localStorage.getItem('hoc-discord-tag'); 75 | const id = localStorage.getItem('hoc-discord-id'); 76 | const ts = localStorage.getItem('hoc-discord-ts'); 77 | 78 | if (tag && id && ts && (Date.now() - ts < 1000 * 5)) { 79 | setDiscordTag(tag); 80 | setDiscordId(id); 81 | } 82 | }, 1000); 83 | return () => clearInterval(interval); 84 | }, []); 85 | 86 | 87 | useEffect(() => { 88 | try { 89 | rdt('track', 'Lead', { 90 | }); 91 | 92 | } catch (err) {} 93 | }, []); 94 | 95 | useEffect(() => { 96 | console.log('Discord stuff changed'); 97 | }, discordTag, discordId) 98 | 99 | // const handleSubmit = (event) => { 100 | // event.preventDefault(); 101 | // // Execute the reCAPTCHA when the form is submitted 102 | // recaptchaRef.current.execute(); 103 | // }; 104 | const handleSubmit = async () => { 105 | // If the reCAPTCHA code is null or undefined indicating that 106 | // the reCAPTCHA was expired then return early 107 | if (!captchaCode) { 108 | return; 109 | } 110 | try { 111 | await splitbee.user.set({ 112 | ...Object.fromEntries(Object.entries(formData).map(([k,v]) => ['FormData' + k.split(' ').join(''), v])) 113 | }); 114 | const response = await fetch("/api/register", { 115 | method: "POST", 116 | body: JSON.stringify({ data: { 117 | ...formData, 118 | ['Discord Tag']: discordTag, 119 | ['Discord ID']: discordId, 120 | }, captcha: captchaCode }), 121 | headers: { 122 | "Content-Type": "application/json", 123 | }, 124 | }); 125 | if (response.ok) { 126 | 127 | // If the response is ok than show the success alert 128 | window.location.href = '/registration/success?name=' + encodeURIComponent(formData['Full Name']); 129 | } else { 130 | // Else throw an error with the message returned 131 | // from the API 132 | const error = await response.json(); 133 | throw new Error(error.message) 134 | } 135 | } catch (error) { 136 | alert(error?.message || "Something went wrong"); 137 | } finally { 138 | // Reset the reCAPTCHA when the request has failed or succeeeded 139 | // so that it can be executed again if user submits another email. 140 | } 141 | }; 142 | 143 | 144 | const [email, setEmail] = useState(''); 145 | const [submitted, setSubmitted] = useState(false); 146 | useEffect(() => { 147 | const params = new Proxy(new URLSearchParams(window.location.search), { 148 | get: (searchParams, prop) => searchParams.get(prop), 149 | }); 150 | if (params?.email) setEmail(params?.email); 151 | if (params?.key) setKey(params?.key); 152 | setShowForm(process.env.NODE_ENV !== "production" || key == process.env.NEXT_PUBLIC_KEY); 153 | }, []); 154 | if (false) return ( 155 | <> 156 |
161 | 162 | Hack OC 163 | 164 | 165 | 166 | ''} hideCloseButton={true}> 167 |
180 |

Registrations will open soon!

181 |

In the meantime, check out our homepage.

182 |
183 |
184 |
185 | 186 | ); 187 | const isDiscordMissing = !discordTag; 188 | const missingLength = valid.missing.length + (isDiscordMissing ? 1 : 0); 189 | 190 | return ( 191 | <> 192 | 193 | Register for Hack OC 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 |
215 | 216 |
217 |

218 | Hack 224 | OC 225 | 232 | 233 | {' '}Registration 234 |

235 |

May 6th • Anduril HQ • 9am-9pm

239 |
e.preventDefault()}> 240 | {sections.map((section, i) => { 241 | return ( 242 |
246 |
258 |
262 | 263 | 264 |
265 |
272 |

{section.title}

273 |

{section.description}

274 |
275 |
276 |
277 |
284 | { 285 | section.questions.map((question, i) => { 286 | return ( 287 | <> 288 |
292 | {(question.special == 'multiSelect' || question.special == 'select') ? 293 |