diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0c60bab --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,67 @@ +# Changelog + +All notable releases to Stadium are documented here. + +## v1.0.0 — First official release + +Stadium is the home for **WebZero's builder programs**. It started as the site for a single hackathon and is now a recurring-events platform: hackathons, the M2 incubator, dogfooding sessions, and PitchOff competitions all live in one place, where builders show their work, the community explores it, and organizers run each event end to end. + +### Who it's for + +- **Visitors & the community** — anyone who wants to browse what's been built and discover teams. +- **Builders & teams** — the people who ship projects in WebZero programs and want them seen, supported, and funded. +- **Organizers & admins** — the WebZero team (and per-event partners) who run programs, review work, and pay out winners. + +--- + +## What you can do with Stadium + +### Discover & explore — for everyone, no account needed + +- **Start from the landing page**, an entry point with a "space" for each program type (Hackathons, M2 Incubator, Dogfooding, PitchOff) that explains what each is. +- **Browse the directory** of every project/entry, with search and filtering, and live index stats (total entries, winners, projects in M2, total paid out). +- **Open any program** to see all of its projects. +- **Dive into a project** across tabs — overview, milestones, team, and updates — including live links (site, repo, demo, slides) and a "seeking funding" signal when a team is raising. +- **See the winners** of past events on dedicated winners pages. +- **Set the vibe** with the hardware-style brightness rack: a time-of-day theme and a SoundCloud player that keeps playing as you move between pages. + +### Show your work & take part — for builders & teams + +- **Sign in with your wallet** using SIWS (Sign-In With Substrate), with multi-chain support across Substrate, Ethereum, and Solana wallets. +- **Apply to a program** as a team — or, if you don't have a Stadium project yet, **apply as a non-member** and Stadium emails the right team on your behalf. +- **Create and edit your project** — name, description, links, tech stack, and categories. +- **Manage your team and payout address**, including per-member roles and socials. +- **Post project updates** to keep the community and organizers in the loop. +- **Raise your hand for funding** by setting a funding signal on your project. +- **Run the M2 incubator track** end to end: agree on milestones, submit your final deliverables, watch your review status, and propose what comes next with a continuation ("what's next, milestone 3?"). +- **Attach a contact email to your wallet** so organizers can reach you. + +### Run your event — for organizers & admins + +- **Sign in two ways:** with an admin **wallet** (SIWS), or via an **email magic link** for view-only program admins who don't use a wallet — with an onboarding email when they're invited. +- **Work within a tiered admin model:** app admins, global admins, and per-program admins, so partners can be scoped to just their event. +- **Create and edit programs** — hackathon, M2 incubator, dogfooding, or PitchOff — with dates, status, and rich, templatable content sections. +- **Manage sponsors** for each program. +- **Collect and triage participation** in one unified inbox that merges in-app applications and signups, **import attendees from a Luma CSV**, and **export the inbox to CSV** (hardened against spreadsheet formula-injection). +- **Apply on behalf of any project** as an admin when you need to. +- **Invite co-admins** by wallet or by email, and manage the admin list per program. +- **Review M2 submissions** — approve them or request changes. +- **Record and confirm payouts** in multiple currencies (USDC / DOT). +- **Keep an audit trail** with a per-program audit log of admin actions. + +### Trust, safety & platform + +- **SIWS verification** always checks the address inside the signed message, never a request header. +- **One sign-in, fewer popups:** a short-lived HMAC session token replaces repeated wallet signatures; the signing secret is validated at server boot. +- **Hardened by default:** security headers (helmet) and rate limiting on sensitive and public endpoints. +- **Privacy-aware:** public project data excludes personal contact information. + +--- + +## Under the hood + +- **Client:** React 18 + Vite + TypeScript + Tailwind + shadcn/ui, deployed to Vercel. +- **Server:** Express 5 (ESM) on Supabase (Postgres), deployed to Railway. +- **Auth:** multi-chain wallet sign-in via SIWS (Substrate, Ethereum, Solana), plus Supabase email magic-link for non-wallet admins. + +Live at . diff --git a/CLAUDE.md b/CLAUDE.md index b2d3a69..390f4e1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,7 @@ Always run from the subdir, not the repo root. - `npm test` — `vitest run` - `npm start` — `node server.js` - `npm run seed:dev`, `npm run db:migrate`, `npm run db:reset` — local Mongo tooling (destructive; run only when asked) -- `npm run verify:production`, `npm run verify:main-deployed`, `npm run deploy:all` — operational +- `npm run verify:production`, `npm run verify:main-deployed` — operational (read-only prod health checks). Deploys are automatic: merging to `main` triggers Railway (server) + Vercel (client) via their GitHub integrations. --- diff --git a/client/.env.example b/client/.env.example index 843d457..9425dae 100644 --- a/client/.env.example +++ b/client/.env.example @@ -2,13 +2,21 @@ # For local development: VITE_API_BASE_URL=http://localhost:2000/api # For production: -# VITE_API_BASE_URL=https://hw4os4c00wg4k80o.apps.joinwebzero.com/api +# VITE_API_BASE_URL=https://stadium-production-996a.up.railway.app/api # Admin wallet addresses (comma-separated). Each entry is either `chain:address` # or a bare address (bare ⇒ substrate). Supported chains: substrate, ethereum. # Example: VITE_ADMIN_ADDRESSES=5Grw...,ethereum:0xAbC123... VITE_ADMIN_ADDRESSES= +# Supabase Auth (social sign-in / email magic link). Used by the client to let +# email-invited program admins sign in without a wallet. Both must be set or +# email sign-in shows as "not configured". The ANON key is public (safe to ship +# to the browser); the server still authorizes via program_admin_emails. +# Get these from the Supabase dashboard (Project Settings → API). +VITE_SUPABASE_URL= +VITE_SUPABASE_ANON_KEY= + # Preview / offline mode: when "true", the client serves fixtures from # src/lib/mockWinners.ts instead of calling the API. Writes are simulated in # localStorage. Production must leave this unset or "false". Set to "true" in diff --git a/client/package-lock.json b/client/package-lock.json index d642fd3..516064f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -43,6 +43,7 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", + "@supabase/supabase-js": "^2.106.1", "@talismn/siws": "^1.0.0", "@tanstack/react-query": "^5.56.2", "bs58": "^6.0.0", @@ -4093,6 +4094,90 @@ "integrity": "sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ==", "license": "Apache-2.0" }, + "node_modules/@supabase/auth-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.106.1.tgz", + "integrity": "sha512-7eyheXfAGwkB9bZewJPs+N3UYt6kra2JG6mIxNEgbkvcO15PLD1e75PTIUEYYl3zrifm3GrpShVl7QZxKrXO/w==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.106.1.tgz", + "integrity": "sha512-XbOPnR2mW7jp/EcW447xmGwCa+/Wc00Hkw8t4tUIJjRsHQ4xAESsLKcyLRhRJjJoUnJVXUlC+w0wUxUCM7CG2A==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz", + "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.106.1.tgz", + "integrity": "sha512-Qbn6d2lqiqeaBX1Uko0e/hL90dtQGRN6CG2wMVQtJpRFstlVW45qmUTyTOsiB8dYUWu1fWYo4YzJuDbokGv3tQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.106.1.tgz", + "integrity": "sha512-eQCYri5E8KsjpDgC7g28cOOS2britjUWdNSJluFMainqrMRepzjOnaxqXc3RoAz7H0dxmBrfLUNF6NGP8C+YaA==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.2", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.106.1.tgz", + "integrity": "sha512-HWcLIhqinhWKpOQ3WzglR2unjW0eh9J7yOu3IZrZNIEkraK4La/HDvTqndljGsNw0itPtyHhuKBxRoPG1VUARw==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.106.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.106.1.tgz", + "integrity": "sha512-gP4HurGkGu7Z3xoOCjtAI17BKKp7jpsmwY0Ssbsks9XQRzJ7ZhK7LxfLdBSYgUdgZCQgjRK+Mr7+cl4Gxrk0Rw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.106.1", + "@supabase/functions-js": "2.106.1", + "@supabase/postgrest-js": "2.106.1", + "@supabase/realtime-js": "2.106.1", + "@supabase/storage-js": "2.106.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@swc/core": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.2.tgz", @@ -6287,6 +6372,15 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", diff --git a/client/package.json b/client/package.json index 0910dd0..dced7bd 100644 --- a/client/package.json +++ b/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", @@ -45,6 +45,7 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", + "@supabase/supabase-js": "^2.106.1", "@talismn/siws": "^1.0.0", "@tanstack/react-query": "^5.56.2", "bs58": "^6.0.0", diff --git a/client/src/App.tsx b/client/src/App.tsx index b580d3b..f0d75ed 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -11,6 +11,7 @@ import ProjectDetailsPage from "./pages/ProjectDetailsPage"; import AdminPage from "./pages/AdminPage"; import NotFound from "./pages/NotFound"; import { useBrightness } from "@/hooks/use-brightness"; +import { SoundCloudAudioProvider } from "@/components/audio/sound-cloud-audio"; import WinnersPage from "./pages/WinnersPage"; import ProgramsPage from "./pages/ProgramsPage"; import ProgramDetailPage from "./pages/ProgramDetailPage"; @@ -35,6 +36,7 @@ const App = () => { + }> @@ -57,6 +59,7 @@ const App = () => { } /> + diff --git a/client/src/components/Layout.tsx b/client/src/components/Layout.tsx index 9ef3e8b..877f8a9 100644 --- a/client/src/components/Layout.tsx +++ b/client/src/components/Layout.tsx @@ -1,14 +1,7 @@ -import { Link, Outlet, useLocation } from "react-router-dom"; +import { Outlet } from "react-router-dom"; +import { SiteFooter } from "@/components/SiteFooter"; const Layout = () => { - const location = useLocation(); - - const isActive = (path: string) => { - if (path === "/" && location.pathname === "/") return true; - if (path !== "/" && location.pathname.startsWith(path)) return true; - return false; - }; - return (
{/* Skip to main content link for keyboard navigation */} @@ -24,76 +17,7 @@ const Layout = () => { - {/* Footer */} - +
); }; diff --git a/client/src/components/M2AgreementSection.tsx b/client/src/components/M2AgreementSection.tsx index 7712d7c..eabc2ce 100644 --- a/client/src/components/M2AgreementSection.tsx +++ b/client/src/components/M2AgreementSection.tsx @@ -125,7 +125,7 @@ export function M2AgreementSection({
- ·CORE FEATURES — MUST COMPLETE + ·CORE FEATURES: MUST COMPLETE
    @@ -184,7 +184,7 @@ export function M2AgreementSection({ ) : (
    -

    ·ROADMAP LOCKED AFTER WEEK 4 — CONTACT YOUR MENTORS IF YOU NEED CHANGES.

    +

    ·ROADMAP LOCKED AFTER WEEK 4. CONTACT YOUR MENTORS IF YOU NEED CHANGES.

    )}
diff --git a/client/src/components/Navigation.tsx b/client/src/components/Navigation.tsx index a840ec0..2c48e33 100644 --- a/client/src/components/Navigation.tsx +++ b/client/src/components/Navigation.tsx @@ -24,7 +24,12 @@ export function Navigation() { online label collapses to just the LED dot on small screens. */}
-
diff --git a/client/src/components/SiteFooter.tsx b/client/src/components/SiteFooter.tsx new file mode 100644 index 0000000..e2a71e8 --- /dev/null +++ b/client/src/components/SiteFooter.tsx @@ -0,0 +1,86 @@ +// Shared site footer. Single source of truth so the credit line + links don't +// drift between Layout-wrapped pages and the standalone detail pages. +export function SiteFooter() { + return ( + + ); +} diff --git a/client/src/components/SubmitM2DeliverablesModal.tsx b/client/src/components/SubmitM2DeliverablesModal.tsx index 984b949..6994678 100644 --- a/client/src/components/SubmitM2DeliverablesModal.tsx +++ b/client/src/components/SubmitM2DeliverablesModal.tsx @@ -110,7 +110,7 @@ export function SubmitM2DeliverablesModal({

- ·BEFORE SUBMITTING — + ·BEFORE SUBMITTING: make sure your code is complete, tested, and documented according to your M2 Agreement roadmap.

@@ -229,7 +229,7 @@ export function SubmitM2DeliverablesModal({

- ·AFTER SUBMISSION — + ·AFTER SUBMISSION: WebZero will review within 2-3 days. You'll be notified via email and can track status on this page.

diff --git a/client/src/components/TeamPaymentSection.tsx b/client/src/components/TeamPaymentSection.tsx index b822a96..cf9ab04 100644 --- a/client/src/components/TeamPaymentSection.tsx +++ b/client/src/components/TeamPaymentSection.tsx @@ -323,7 +323,7 @@ export function TeamPaymentSection({ {/* Row 1: Name and Role */}
- + updateMember(index, 'name', e.target.value)} @@ -332,7 +332,7 @@ export function TeamPaymentSection({ />
- + updateMember(index, 'role', e.target.value)} @@ -345,7 +345,7 @@ export function TeamPaymentSection({ {/* Row 2: Wallet Address + chain */}
- + updateMember(index, 'walletAddress', e.target.value)} @@ -354,7 +354,7 @@ export function TeamPaymentSection({ />
- +
-