diff --git a/package-lock.json b/package-lock.json index 5b57b51..128030f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -962,7 +962,6 @@ "version": "2.49.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -1000,7 +999,6 @@ "version": "6.2.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", @@ -1047,7 +1045,6 @@ "integrity": "sha512-czWPzKIAXucn9PtsttxmumiQ9N0ok9FrBwgRWrwmVLlp86BrMExzvXRLFYRJ+Ex3g6yqj+KuaxfX1JTgV2lpfg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1062,7 +1059,6 @@ "node_modules/acorn": { "version": "8.15.0", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1470,7 +1466,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -1713,7 +1708,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -1791,7 +1785,6 @@ "version": "4.53.3", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -1889,7 +1882,6 @@ "node_modules/svelte": { "version": "5.45.6", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1978,7 +1970,6 @@ "version": "5.9.3", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1998,7 +1989,6 @@ "version": "7.2.7", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/src/lib/Button.svelte b/src/lib/Button.svelte index 3413d16..372e6c0 100644 --- a/src/lib/Button.svelte +++ b/src/lib/Button.svelte @@ -1,62 +1,116 @@ {#if href} - - + + {:else} - {/if} diff --git a/src/lib/Present.svelte b/src/lib/Present.svelte index 5d479c0..2054981 100644 --- a/src/lib/Present.svelte +++ b/src/lib/Present.svelte @@ -1,35 +1,81 @@ - diff --git a/src/lib/ProgressTracker.svelte b/src/lib/ProgressTracker.svelte new file mode 100644 index 0000000..81bbb06 --- /dev/null +++ b/src/lib/ProgressTracker.svelte @@ -0,0 +1,161 @@ + + +
+
+ Progress + {completedDays}/{totalDays} days +
+ +
+
+ + {#each milestones as milestone} +
= milestone.days} + style="left: {(milestone.days / totalDays) * 100}%" + title="{milestone.days} days: {milestone.reward}" + > +
+
+ {/each} +
+ +
+ {#each milestones as milestone} +
= milestone.days}> + {milestone.days} + {milestone.reward} +
+ {/each} +
+
+ + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 022734c..a9ff519 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -9,63 +9,75 @@ + + -{@render children()} +
+ {@render children()} +
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..ff0e576 --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,72 @@ +import { AIRTABLE_API_KEY, AIRTABLE_BASE_ID } from '$env/static/private'; +import type { PageServerLoad } from './$types'; + +interface AirtableAttachment { + id: string; + url: string; + filename: string; + type: string; + thumbnails?: { + small: { url: string }; + large: { url: string }; + }; +} + +interface Project { + id: string; + codeUrl: string | null; + playableUrl: string | null; + screenshot: string | null; + status: 'Approved' | 'Pending Review'; +} + +interface AirtableRecord { + id: string; + fields: { + 'Code URL'?: string; + 'Playable URL'?: string; + Email?: string; + Screenshot?: AirtableAttachment[]; + 'Automation - Status'?: string; + }; +} + +interface AirtableResponse { + records: AirtableRecord[]; +} + +export const load: PageServerLoad = async ({ locals }) => { + // If user is not authenticated, return 0 completed days + if (!locals.user) { + return { completedDays: 0 }; + } + + const filterFormula = encodeURIComponent(`{Email} = '${locals.user.email}'`); + + const response = await fetch( + `https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/YSWS%20Project%20Submission?filterByFormula=${filterFormula}`, + { + headers: { + Authorization: `Bearer ${AIRTABLE_API_KEY}`, + 'Content-Type': 'application/json' + } + } + ); + + if (!response.ok) { + console.error('Airtable fetch failed:', response.statusText); + return { completedDays: 0 }; + } + + const data: AirtableResponse = await response.json(); + + const projects: Project[] = data.records.map((record) => ({ + id: record.id, + codeUrl: record.fields['Code URL'] ?? null, + playableUrl: record.fields['Playable URL'] ?? null, + screenshot: record.fields['Screenshot']?.[0]?.url ?? null, + status: record.fields['Automation - Status'] === '2–Submitted' ? 'Approved' : 'Pending Review' + })); + + return { completedDays: projects.length }; +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8cc5e66..ab15fab 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,7 +1,13 @@ - - -
-

Haxmas

+ + Haxmas - 12 Days of Workshops + -
- {#each days as day} - goToDay(day.url)} - /> - {/each} -
- -
- - - - - - -
-
+ +
+ +
+
+

12 days of workshops. Epic prizes. December 13-25.

+
+ +
+ + +
+ {#each days as day} + goToDay(day.url, day.day)} + /> + {/each} +
+ + +
+
diff --git a/src/routes/credits/+page.svelte b/src/routes/credits/+page.svelte index 4481b01..4bcf48d 100644 --- a/src/routes/credits/+page.svelte +++ b/src/routes/credits/+page.svelte @@ -1,7 +1,5 @@ - -
-

Organizers

- -
- {#each orgs as org} - - {/each} -
- -

Ambassadors

- -
- {#each ambassadors as org} - - {/each} + + Credits | Haxmas + + +
+ +
+
+
+

Organizers

+
+ {#each orgs as person} + + {/each} +
+
+ +
+

Workshop Ambassadors

+
+ {#each ambassadors as person} + + {/each} +
+
+ +
- -
- Haxmas is proudly 100% teen-organized. -
-
diff --git a/src/routes/day/[day]/+page.svelte b/src/routes/day/[day]/+page.svelte index bcad3b9..897d27a 100644 --- a/src/routes/day/[day]/+page.svelte +++ b/src/routes/day/[day]/+page.svelte @@ -4,6 +4,8 @@ import hljs from 'highlight.js'; import { onMount } from 'svelte'; import { CURRENT_DAY } from '../../../consts'; + import Header from '$lib/Header.svelte'; + import Button from '$lib/Button.svelte'; type PageProps = { data: { @@ -16,6 +18,7 @@ let markdownContent = $state(''); let htmlContent = $state(''); let isClosed = $state(false); + let isLoading = $state(true); function isDayClosed(day: number): boolean { const openDateEST = new Date(`2025-12-${day + 12}T00:00:00-05:00`); @@ -36,11 +39,13 @@ onMount(async () => { const day = parseInt(data.day, 10); if (day < 1 || day > 12 || isNaN(day) || !Number.isInteger(day)) { - htmlContent = '

Invalid day.

Please select a day between 1 and 12.

'; + htmlContent = '

Invalid day

Please select a day between 1 and 12.

'; + isLoading = false; return; } - if (day > CURRENT_DAY && day != 4) { - htmlContent = `

Day ${day} is not available yet.

Please come back on December ${day + 12} to see the content!

`; + if (day > CURRENT_DAY && day !== 4) { + htmlContent = `

Day ${day} is not available yet

Come back on December ${day + 12} to see the content!

`; + isLoading = false; return; } const repo = day === 9 ? 'jeninh' : 'hackclub'; @@ -52,6 +57,7 @@ htmlContent = await marked(markdownContent); isClosed = isDayClosed(day); } + isLoading = false; }); @@ -63,38 +69,40 @@ /> -
-
- ← Back to Home +
- {#if isClosed} -
- Closed - This day is no longer accepting submissions +
+
+ {#if isLoading} +
+
+

Loading workshop...

+
+ {:else} + {#if isClosed} +
+ ⚠️ This day is closed and no longer accepting submissions +
+ {/if} + +
+ {@html htmlContent} +
+ +
+
{/if} - -
- {@html htmlContent} -
diff --git a/src/routes/faq/+page.svelte b/src/routes/faq/+page.svelte index 9203cc5..cdd8411 100644 --- a/src/routes/faq/+page.svelte +++ b/src/routes/faq/+page.svelte @@ -1,19 +1,23 @@ + + FAQ | Haxmas -
-
-

FAQ

+
+
+

How does this work?

Welcome to Haxmas; 12 days of hands-on hacking! From December 13-25, a new workshop drops each day, created by a different member of the Hack Club community. Each workshop is only - available on its specific day (day 1's workshop on December 13, day 2's workshop on December - 14, and so on), so make sure to participate daily! Each workshop comes with its own special - prize, and you'll earn snowflakes that you can redeem for stickers, swag, and more! + available on its specific day, so make sure to participate daily! Each workshop comes with + its own special prize, and you'll earn snowflakes that you can redeem for stickers, swag, + and more!

The more days you participate, the better the rewards:

    @@ -58,67 +62,68 @@

    Is Haxmas free?

    - Yes! Haxmas is a Hack Club program and completely free to participate. Find out more at hackclub.com. + Yes! Haxmas is a Hack Club program and completely free to participate. Find out more at + hackclub.com.

-
+
diff --git a/src/routes/shop/+page.server.ts b/src/routes/shop/+page.server.ts index 796f418..b050b4b 100644 --- a/src/routes/shop/+page.server.ts +++ b/src/routes/shop/+page.server.ts @@ -40,15 +40,12 @@ export const load: PageServerLoad = async ({ locals }) => { throw redirect(302, '/landing'); } - const response = await fetch( - `https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/Shop`, - { - headers: { - Authorization: `Bearer ${AIRTABLE_API_KEY}`, - 'Content-Type': 'application/json' - } + const response = await fetch(`https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/Shop`, { + headers: { + Authorization: `Bearer ${AIRTABLE_API_KEY}`, + 'Content-Type': 'application/json' } - ); + }); if (!response.ok) { console.error('Airtable fetch failed:', response.statusText); diff --git a/src/routes/shop/+page.svelte b/src/routes/shop/+page.svelte index b575ed6..a5c0e3b 100644 --- a/src/routes/shop/+page.svelte +++ b/src/routes/shop/+page.svelte @@ -1,165 +1,186 @@ -
-

Shop

- -
- {#each data.items as item} -
- {#if item.image} - {item.name} - {:else} -
Image Coming Soon
- {/if} -
-

{item.name}

-

{item.cost}

-
-
- {/each} -
+ + Shop | Haxmas + + +
- {#if data.items.length === 0} -

No items available in the shop right now.

- {/if} +
+
+

Spend your snowflakes on awesome rewards!

- + {#if !data.items || data.items.length === 0} +
+ 🛒 +

No items available in the shop right now.

+

Check back soon for new items!

+
+ {:else} +
+ {#each data.items as item} +
+ {#if item.image} + {item.name} + {:else} +
+ Image Coming Soon +
+ {/if} +
+

{item.name}

+
+ ❄️ + {item.cost} +
+
+
+ {/each} +
+ {/if} +
diff --git a/src/routes/submit/+page.svelte b/src/routes/submit/+page.svelte index e79e58c..a9e62dc 100644 --- a/src/routes/submit/+page.svelte +++ b/src/routes/submit/+page.svelte @@ -1,167 +1,165 @@ - + + Submit | Haxmas + -
-

Choose a day to submit

- -
- {#each days as day} - goToDay(day.url)} - /> - {/each} -
- -
- -
-
+ +
+ +
+
+

Choose a day to submit your project

+
+ +
+ {#each days as day} + goToDay(day.url, day.day)} + /> + {/each} +
+