diff --git a/app/api/create-checkout-session/route.ts b/app/api/create-checkout-session/route.ts new file mode 100644 index 0000000..108423d --- /dev/null +++ b/app/api/create-checkout-session/route.ts @@ -0,0 +1,86 @@ +import { type NextRequest, NextResponse } from "next/server" +import Stripe from "stripe" + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: "2024-12-18.acacia", +}) + +export async function POST(request: NextRequest) { + try { + const formData = await request.formData() + const paymentMethod = formData.get("paymentMethod") as string + const registrationDataStr = formData.get("registrationData") as string + + // Parse registration data + const registrationData = registrationDataStr ? JSON.parse(registrationDataStr) : null + + // Configure payment method types based on selection + let paymentMethodTypes: Stripe.Checkout.SessionCreateParams.PaymentMethodType[] = ["card"] + + if (paymentMethod === "sepa_debit") { + paymentMethodTypes = ["sepa_debit"] + } else if (paymentMethod === "crypto") { + paymentMethodTypes = ["customer_balance"] + } + + const session = await stripe.checkout.sessions.create({ + payment_method_types: paymentMethodTypes, + line_items: [ + { + price_data: { + currency: "eur", + product_data: { + name: "Crypto Commons Gathering 2026", + description: "Full event access including all sessions and infrastructure", + images: ["https://ccg2025.vercel.app/og-image.png"], + }, + unit_amount: 20000, // €200 + }, + quantity: 1, + adjustable_quantity: { + enabled: true, + minimum: 1, + maximum: 5, + }, + }, + ], + mode: "payment", + success_url: `${request.nextUrl.origin}/success?session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${request.nextUrl.origin}/register`, + metadata: registrationData + ? { + name: registrationData.name, + contact: registrationData.contact, + contributions: registrationData.contributions.substring(0, 500), // Stripe has length limits + expectations: registrationData.expectations.substring(0, 500), + howHeard: registrationData.howHeard || "", + dietary: + registrationData.dietary.join(", ") + + (registrationData.dietaryOther ? `, ${registrationData.dietaryOther}` : ""), + crewConsent: registrationData.crewConsent, + } + : {}, + // For stablecoin payments + ...(paymentMethod === "crypto" && { + payment_method_options: { + customer_balance: { + funding_type: "crypto", + bank_transfer: { + type: "crypto", + }, + }, + }, + }), + allow_promotion_codes: true, + billing_address_collection: "required", + phone_number_collection: { + enabled: true, + }, + }) + + return NextResponse.redirect(session.url!) + } catch (err) { + console.error("[v0] Error creating checkout session:", err) + return NextResponse.json({ error: "Error creating checkout session" }, { status: 500 }) + } +} diff --git a/app/api/webhook/route.ts b/app/api/webhook/route.ts new file mode 100644 index 0000000..b5b050f --- /dev/null +++ b/app/api/webhook/route.ts @@ -0,0 +1,56 @@ +import { type NextRequest, NextResponse } from "next/server" +import Stripe from "stripe" + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: "2024-12-18.acacia", +}) + +const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET! + +export async function POST(request: NextRequest) { + try { + const body = await request.text() + const signature = request.headers.get("stripe-signature")! + + let event: Stripe.Event + + try { + event = stripe.webhooks.constructEvent(body, signature, webhookSecret) + } catch (err) { + console.error("[v0] Webhook signature verification failed:", err) + return NextResponse.json({ error: "Invalid signature" }, { status: 400 }) + } + + // Handle the event + switch (event.type) { + case "checkout.session.completed": { + const session = event.data.object as Stripe.Checkout.Session + console.log("[v0] Payment successful:", session.id) + + // Here you would: + // 1. Store the registration in your database + // 2. Send confirmation email + // 3. Add to attendee list + + break + } + case "payment_intent.succeeded": { + const paymentIntent = event.data.object as Stripe.PaymentIntent + console.log("[v0] PaymentIntent successful:", paymentIntent.id) + break + } + case "payment_intent.payment_failed": { + const paymentIntent = event.data.object as Stripe.PaymentIntent + console.error("[v0] Payment failed:", paymentIntent.id) + break + } + default: + console.log("[v0] Unhandled event type:", event.type) + } + + return NextResponse.json({ received: true }) + } catch (err) { + console.error("[v0] Webhook error:", err) + return NextResponse.json({ error: "Webhook error" }, { status: 500 }) + } +} diff --git a/app/globals.css b/app/globals.css index dcd2079..e478d9d 100644 --- a/app/globals.css +++ b/app/globals.css @@ -4,74 +4,76 @@ @custom-variant dark (&:is(.dark *)); :root { - --background: oklch(0.985 0.005 120); - --foreground: oklch(0.22 0.02 150); - --card: oklch(0.99 0.003 120); - --card-foreground: oklch(0.22 0.02 150); - --popover: oklch(0.99 0.003 120); - --popover-foreground: oklch(0.22 0.02 150); - --primary: oklch(0.35 0.08 155); - --primary-foreground: oklch(0.99 0.003 120); - --secondary: oklch(0.92 0.01 120); - --secondary-foreground: oklch(0.22 0.02 150); - --muted: oklch(0.94 0.008 120); - --muted-foreground: oklch(0.52 0.015 150); - --accent: oklch(0.45 0.12 160); - --accent-foreground: oklch(0.99 0.003 120); + /* Updated color scheme to white and red theme inspired by crypto Commons association */ + --background: oklch(0.99 0.002 30); + --foreground: oklch(0.2 0.01 30); + --card: oklch(0.995 0.001 30); + --card-foreground: oklch(0.2 0.01 30); + --popover: oklch(0.995 0.001 30); + --popover-foreground: oklch(0.2 0.01 30); + --primary: oklch(0.55 0.22 25); + --primary-foreground: oklch(0.99 0.002 30); + --secondary: oklch(0.95 0.005 30); + --secondary-foreground: oklch(0.2 0.01 30); + --muted: oklch(0.96 0.003 30); + --muted-foreground: oklch(0.5 0.01 30); + --accent: oklch(0.45 0.2 25); + --accent-foreground: oklch(0.99 0.002 30); --destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.985 0 0); - --border: oklch(0.88 0.01 120); - --input: oklch(0.88 0.01 120); - --ring: oklch(0.45 0.12 160); - --chart-1: oklch(0.45 0.12 160); - --chart-2: oklch(0.62 0.15 140); - --chart-3: oklch(0.38 0.09 155); - --chart-4: oklch(0.72 0.1 125); - --chart-5: oklch(0.55 0.14 145); + --border: oklch(0.9 0.01 30); + --input: oklch(0.9 0.01 30); + --ring: oklch(0.55 0.22 25); + --chart-1: oklch(0.55 0.22 25); + --chart-2: oklch(0.65 0.18 30); + --chart-3: oklch(0.45 0.2 20); + --chart-4: oklch(0.75 0.15 35); + --chart-5: oklch(0.6 0.19 28); --radius: 0.5rem; - --sidebar: oklch(0.985 0.005 120); - --sidebar-foreground: oklch(0.22 0.02 150); - --sidebar-primary: oklch(0.35 0.08 155); - --sidebar-primary-foreground: oklch(0.99 0.003 120); - --sidebar-accent: oklch(0.92 0.01 120); - --sidebar-accent-foreground: oklch(0.22 0.02 150); - --sidebar-border: oklch(0.88 0.01 120); - --sidebar-ring: oklch(0.45 0.12 160); + --sidebar: oklch(0.99 0.002 30); + --sidebar-foreground: oklch(0.2 0.01 30); + --sidebar-primary: oklch(0.55 0.22 25); + --sidebar-primary-foreground: oklch(0.99 0.002 30); + --sidebar-accent: oklch(0.95 0.005 30); + --sidebar-accent-foreground: oklch(0.2 0.01 30); + --sidebar-border: oklch(0.9 0.01 30); + --sidebar-ring: oklch(0.55 0.22 25); } .dark { - --background: oklch(0.18 0.015 150); - --foreground: oklch(0.96 0.005 120); - --card: oklch(0.22 0.018 150); - --card-foreground: oklch(0.96 0.005 120); - --popover: oklch(0.22 0.018 150); - --popover-foreground: oklch(0.96 0.005 120); - --primary: oklch(0.62 0.15 140); - --primary-foreground: oklch(0.18 0.015 150); - --secondary: oklch(0.28 0.02 150); - --secondary-foreground: oklch(0.96 0.005 120); - --muted: oklch(0.26 0.02 150); - --muted-foreground: oklch(0.68 0.01 130); - --accent: oklch(0.55 0.14 145); - --accent-foreground: oklch(0.96 0.005 120); + /* Updated dark mode to complement the red/white theme */ + --background: oklch(0.15 0.01 30); + --foreground: oklch(0.96 0.002 30); + --card: oklch(0.18 0.012 30); + --card-foreground: oklch(0.96 0.002 30); + --popover: oklch(0.18 0.012 30); + --popover-foreground: oklch(0.96 0.002 30); + --primary: oklch(0.65 0.2 28); + --primary-foreground: oklch(0.15 0.01 30); + --secondary: oklch(0.25 0.015 30); + --secondary-foreground: oklch(0.96 0.002 30); + --muted: oklch(0.23 0.015 30); + --muted-foreground: oklch(0.65 0.01 30); + --accent: oklch(0.6 0.19 25); + --accent-foreground: oklch(0.96 0.002 30); --destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.985 0 0); - --border: oklch(0.32 0.025 150); - --input: oklch(0.32 0.025 150); - --ring: oklch(0.55 0.14 145); - --chart-1: oklch(0.62 0.15 140); - --chart-2: oklch(0.72 0.1 125); - --chart-3: oklch(0.55 0.14 145); - --chart-4: oklch(0.45 0.12 160); - --chart-5: oklch(0.82 0.08 135); - --sidebar: oklch(0.22 0.018 150); - --sidebar-foreground: oklch(0.96 0.005 120); - --sidebar-primary: oklch(0.62 0.15 140); - --sidebar-primary-foreground: oklch(0.96 0.005 120); - --sidebar-accent: oklch(0.28 0.02 150); - --sidebar-accent-foreground: oklch(0.96 0.005 120); - --sidebar-border: oklch(0.32 0.025 150); - --sidebar-ring: oklch(0.55 0.14 145); + --border: oklch(0.28 0.02 30); + --input: oklch(0.28 0.02 30); + --ring: oklch(0.6 0.19 25); + --chart-1: oklch(0.65 0.2 28); + --chart-2: oklch(0.75 0.15 35); + --chart-3: oklch(0.6 0.19 25); + --chart-4: oklch(0.55 0.22 25); + --chart-5: oklch(0.85 0.12 32); + --sidebar: oklch(0.18 0.012 30); + --sidebar-foreground: oklch(0.96 0.002 30); + --sidebar-primary: oklch(0.65 0.2 28); + --sidebar-primary-foreground: oklch(0.96 0.002 30); + --sidebar-accent: oklch(0.25 0.015 30); + --sidebar-accent-foreground: oklch(0.96 0.002 30); + --sidebar-border: oklch(0.28 0.02 30); + --sidebar-ring: oklch(0.6 0.19 25); } @theme inline { diff --git a/app/layout.tsx b/app/layout.tsx index edc2f5b..887982a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -8,9 +8,9 @@ const _geist = Geist({ subsets: ["latin"] }) const _geistMono = Geist_Mono({ subsets: ["latin"] }) export const metadata: Metadata = { - title: "Crypto Commons Gathering 2025 | CCG", + title: "Crypto Commons Gathering 2026 | CCG", description: - "A hack-ademic confluence of commons praxis and the latest cryptographic technologies in the Austrian Alps. August 24-29, 2025.", + "The sixth annual hack-ademic confluence of commons praxis and the latest cryptographic technologies in the Austrian Alps. August 2026.", generator: "v0.app", icons: { icon: [ diff --git a/app/page.tsx b/app/page.tsx index 9e1760b..52b06f9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,27 +8,25 @@ export default function HomePage() {
{/* Hero Section */}
-
- - {/* Background pattern */}
+
-
-

August 24-29, 2025 • Fifth Edition

-
-

Crypto Commons
Gathering

+
+

August 24-28, 2026

+
+

A hack-ademic confluence of commons praxis and the latest cryptographic technologies, set in the foothills of the Austrian Alps. @@ -36,15 +34,11 @@ export default function HomePage() {

-
- Activists, Researchers, Hackers, Builders + Hackers, Academics, Activists, Researchers, Builders +
+
+
+
+ + {/* Community Photos Section */} +
+
+
+
+ CCG participants in discussion circle +
+
+ Community meal at CCG +
+
+ Hands-on hacking at CCG
@@ -86,7 +101,7 @@ export default function HomePage() { corporate control.

- What began in 2021 as a modest gathering has evolved into an annual moment of (re)connection and + What began in 2020 as a modest gathering has evolved into an annual moment of (re)connection and reflection for a growing community of crypto-commons thinkers from around the world.

@@ -165,20 +180,29 @@ export default function HomePage() { {/* Unconference Format */} -
-
-

What is an Unconference?

-

+

+
+
+
+

+ What is an Unconference? +

+

An unconference is a participant-driven event format that emphasizes open, flexible, and spontaneous discussions rather than traditional pre-planned presentations. Unlike conventional conferences, unconferences have no predefined agenda or speakers.

-

+

Attendees collaboratively propose topics, sessions, and activities on the spot. Participants vote or self-organize around topics they find most interesting, forming small groups or breakout sessions where discussions, workshops, or hands-on activities take place.

-

+

Everyone is considered equally qualified to contribute, share insights, and guide conversations. Bring your ideas for talks, roundtables, workshops, prototyping sessions, board game nights, LARPs, and more.

@@ -240,26 +264,20 @@ export default function HomePage() { {/* CTA Section */}
-

Join the Fifth Edition of CCG

+

Join the Sixth Edition of CCG

- Whether you're reconnecting or arriving for the first time, CCG 2025 presents a space to collectively take + Whether you're reconnecting or arriving for the first time, CCG 2026 presents a space to collectively take stock of what crypto commons has become and what it still could be.

@@ -274,11 +292,11 @@ export default function HomePage() {
-

CCG 2025

+

CCG 2026

Crypto Commons Gathering
- August 24-29, 2025 + August 2026

@@ -290,22 +308,21 @@ export default function HomePage() { href="https://ccg2025.vercel.app/about" className="text-muted-foreground hover:text-foreground transition-colors" > - About CCG 2025 + About CCG 2026
  • Directions
  • - + Financial Transparency
  • @@ -355,7 +372,7 @@ export default function HomePage() {
    -

    © 2025 Crypto Commons Gathering. Built with solidarity for the commons.

    +

    © 2026 Crypto Commons Gathering. Built with solidarity for the commons.

    diff --git a/app/register/page.tsx b/app/register/page.tsx new file mode 100644 index 0000000..24c8556 --- /dev/null +++ b/app/register/page.tsx @@ -0,0 +1,375 @@ +"use client" + +import type React from "react" + +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Textarea } from "@/components/ui/textarea" +import { Checkbox } from "@/components/ui/checkbox" +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" +import Link from "next/link" +import { useState } from "react" + +export default function RegisterPage() { + const [step, setStep] = useState<"form" | "payment">("form") + const [formData, setFormData] = useState({ + name: "", + contact: "", + contributions: "", + expectations: "", + howHeard: "", + dietary: [] as string[], + dietaryOther: "", + crewConsent: "", + }) + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + // Validate required fields + if ( + !formData.name || + !formData.contact || + !formData.contributions || + !formData.expectations || + !formData.crewConsent + ) { + alert("Please fill in all required fields") + return + } + setStep("payment") + } + + const handleDietaryChange = (value: string, checked: boolean) => { + setFormData((prev) => ({ + ...prev, + dietary: checked ? [...prev.dietary, value] : prev.dietary.filter((item) => item !== value), + })) + } + + if (step === "payment") { + return ( +
    + {/* Header */} +
    +
    + + CCG + +
    +
    + +
    +
    +

    Complete Your Registration

    +

    Choose your payment method

    +
    + + + + Event Cost Breakdown + Full 6-day event costs (August 16-22, 2026) + + +
    +
    +
    +
    Ticket Price
    +
    Venue rental & infrastructure
    +
    + €200 +
    +
    +
    +
    Accommodation
    +
    6 nights dorm (€37.90/night)
    +
    + €227.40 +
    +
    +
    +
    Food
    +
    6 days meals (€22.50/day avg)
    +
    + €135 +
    +
    +
    +
    Total Estimated Cost
    +
    Pay ticket now, accommodation & food at venue
    +
    + €562.40 +
    +
    +

    + You're only paying the ticket price (€200) now. Accommodation and food will be arranged and paid + separately at the Commons Hub. +

    +
    +
    + {/* */} + + {/* Payment Methods */} +
    + + + Payment Options + Ticket Price: €200 per person + + +
    + + +
    +
    + + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + +
    +

    + All payments are processed securely through Stripe. You'll receive a confirmation email after successful + payment. +

    +
    +
    +
    +
    + ) + } + + return ( +
    + {/* Header */} +
    +
    + + CCG + +
    +
    + +
    +
    +

    Register for CCG 2026

    +

    August 16-22, 2026 at the Commons Hub in Austria

    +
    + + + + Registration Form + Tell us about yourself and what you'd like to bring to CCG + + +
    + {/* Name */} +
    + + setFormData({ ...formData, name: e.target.value })} + /> +
    + + {/* Contact */} +
    + + setFormData({ ...formData, contact: e.target.value })} + /> +
    + + {/* Contributions */} +
    + +