feat: update color scheme to crypto-themed palette

Switch to white, red, and neutral gray for consistent contrast.

#VERCEL_SKIP

Co-authored-by: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com>
This commit is contained in:
v0 2025-12-06 14:22:52 +00:00
parent 82ff0f5516
commit a3cf6995b2
17 changed files with 1461 additions and 6091 deletions

View File

@ -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 })
}
}

56
app/api/webhook/route.ts Normal file
View File

@ -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 })
}
}

View File

@ -4,74 +4,76 @@
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
:root { :root {
--background: oklch(0.985 0.005 120); /* Updated color scheme to white and red theme inspired by crypto Commons association */
--foreground: oklch(0.22 0.02 150); --background: oklch(0.99 0.002 30);
--card: oklch(0.99 0.003 120); --foreground: oklch(0.2 0.01 30);
--card-foreground: oklch(0.22 0.02 150); --card: oklch(0.995 0.001 30);
--popover: oklch(0.99 0.003 120); --card-foreground: oklch(0.2 0.01 30);
--popover-foreground: oklch(0.22 0.02 150); --popover: oklch(0.995 0.001 30);
--primary: oklch(0.35 0.08 155); --popover-foreground: oklch(0.2 0.01 30);
--primary-foreground: oklch(0.99 0.003 120); --primary: oklch(0.55 0.22 25);
--secondary: oklch(0.92 0.01 120); --primary-foreground: oklch(0.99 0.002 30);
--secondary-foreground: oklch(0.22 0.02 150); --secondary: oklch(0.95 0.005 30);
--muted: oklch(0.94 0.008 120); --secondary-foreground: oklch(0.2 0.01 30);
--muted-foreground: oklch(0.52 0.015 150); --muted: oklch(0.96 0.003 30);
--accent: oklch(0.45 0.12 160); --muted-foreground: oklch(0.5 0.01 30);
--accent-foreground: oklch(0.99 0.003 120); --accent: oklch(0.45 0.2 25);
--accent-foreground: oklch(0.99 0.002 30);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.985 0 0); --destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.88 0.01 120); --border: oklch(0.9 0.01 30);
--input: oklch(0.88 0.01 120); --input: oklch(0.9 0.01 30);
--ring: oklch(0.45 0.12 160); --ring: oklch(0.55 0.22 25);
--chart-1: oklch(0.45 0.12 160); --chart-1: oklch(0.55 0.22 25);
--chart-2: oklch(0.62 0.15 140); --chart-2: oklch(0.65 0.18 30);
--chart-3: oklch(0.38 0.09 155); --chart-3: oklch(0.45 0.2 20);
--chart-4: oklch(0.72 0.1 125); --chart-4: oklch(0.75 0.15 35);
--chart-5: oklch(0.55 0.14 145); --chart-5: oklch(0.6 0.19 28);
--radius: 0.5rem; --radius: 0.5rem;
--sidebar: oklch(0.985 0.005 120); --sidebar: oklch(0.99 0.002 30);
--sidebar-foreground: oklch(0.22 0.02 150); --sidebar-foreground: oklch(0.2 0.01 30);
--sidebar-primary: oklch(0.35 0.08 155); --sidebar-primary: oklch(0.55 0.22 25);
--sidebar-primary-foreground: oklch(0.99 0.003 120); --sidebar-primary-foreground: oklch(0.99 0.002 30);
--sidebar-accent: oklch(0.92 0.01 120); --sidebar-accent: oklch(0.95 0.005 30);
--sidebar-accent-foreground: oklch(0.22 0.02 150); --sidebar-accent-foreground: oklch(0.2 0.01 30);
--sidebar-border: oklch(0.88 0.01 120); --sidebar-border: oklch(0.9 0.01 30);
--sidebar-ring: oklch(0.45 0.12 160); --sidebar-ring: oklch(0.55 0.22 25);
} }
.dark { .dark {
--background: oklch(0.18 0.015 150); /* Updated dark mode to complement the red/white theme */
--foreground: oklch(0.96 0.005 120); --background: oklch(0.15 0.01 30);
--card: oklch(0.22 0.018 150); --foreground: oklch(0.96 0.002 30);
--card-foreground: oklch(0.96 0.005 120); --card: oklch(0.18 0.012 30);
--popover: oklch(0.22 0.018 150); --card-foreground: oklch(0.96 0.002 30);
--popover-foreground: oklch(0.96 0.005 120); --popover: oklch(0.18 0.012 30);
--primary: oklch(0.62 0.15 140); --popover-foreground: oklch(0.96 0.002 30);
--primary-foreground: oklch(0.18 0.015 150); --primary: oklch(0.65 0.2 28);
--secondary: oklch(0.28 0.02 150); --primary-foreground: oklch(0.15 0.01 30);
--secondary-foreground: oklch(0.96 0.005 120); --secondary: oklch(0.25 0.015 30);
--muted: oklch(0.26 0.02 150); --secondary-foreground: oklch(0.96 0.002 30);
--muted-foreground: oklch(0.68 0.01 130); --muted: oklch(0.23 0.015 30);
--accent: oklch(0.55 0.14 145); --muted-foreground: oklch(0.65 0.01 30);
--accent-foreground: oklch(0.96 0.005 120); --accent: oklch(0.6 0.19 25);
--accent-foreground: oklch(0.96 0.002 30);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.985 0 0); --destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.32 0.025 150); --border: oklch(0.28 0.02 30);
--input: oklch(0.32 0.025 150); --input: oklch(0.28 0.02 30);
--ring: oklch(0.55 0.14 145); --ring: oklch(0.6 0.19 25);
--chart-1: oklch(0.62 0.15 140); --chart-1: oklch(0.65 0.2 28);
--chart-2: oklch(0.72 0.1 125); --chart-2: oklch(0.75 0.15 35);
--chart-3: oklch(0.55 0.14 145); --chart-3: oklch(0.6 0.19 25);
--chart-4: oklch(0.45 0.12 160); --chart-4: oklch(0.55 0.22 25);
--chart-5: oklch(0.82 0.08 135); --chart-5: oklch(0.85 0.12 32);
--sidebar: oklch(0.22 0.018 150); --sidebar: oklch(0.18 0.012 30);
--sidebar-foreground: oklch(0.96 0.005 120); --sidebar-foreground: oklch(0.96 0.002 30);
--sidebar-primary: oklch(0.62 0.15 140); --sidebar-primary: oklch(0.65 0.2 28);
--sidebar-primary-foreground: oklch(0.96 0.005 120); --sidebar-primary-foreground: oklch(0.96 0.002 30);
--sidebar-accent: oklch(0.28 0.02 150); --sidebar-accent: oklch(0.25 0.015 30);
--sidebar-accent-foreground: oklch(0.96 0.005 120); --sidebar-accent-foreground: oklch(0.96 0.002 30);
--sidebar-border: oklch(0.32 0.025 150); --sidebar-border: oklch(0.28 0.02 30);
--sidebar-ring: oklch(0.55 0.14 145); --sidebar-ring: oklch(0.6 0.19 25);
} }
@theme inline { @theme inline {

View File

@ -8,9 +8,9 @@ const _geist = Geist({ subsets: ["latin"] })
const _geistMono = Geist_Mono({ subsets: ["latin"] }) const _geistMono = Geist_Mono({ subsets: ["latin"] })
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Crypto Commons Gathering 2025 | CCG", title: "Crypto Commons Gathering 2026 | CCG",
description: 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", generator: "v0.app",
icons: { icons: {
icon: [ icon: [

View File

@ -8,27 +8,25 @@ export default function HomePage() {
<div className="min-h-screen"> <div className="min-h-screen">
{/* Hero Section */} {/* Hero Section */}
<section className="relative min-h-[90vh] flex items-center justify-center overflow-hidden"> <section className="relative min-h-[90vh] flex items-center justify-center overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-accent/5 to-background z-0" />
{/* Background pattern */}
<div <div
className="absolute inset-0 opacity-[0.03]" className="absolute inset-0 bg-cover bg-center z-0"
style={{ style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fillRule='evenodd'%3E%3Cg fill='%23000000' fillOpacity='1'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`, backgroundImage: `url('/images/image.png')`,
}} }}
/> />
<div className="absolute inset-0 bg-gradient-to-b from-background/80 via-background/70 to-background z-[1]" />
<div className="container mx-auto px-4 relative z-10 text-center max-w-5xl"> <div className="container mx-auto px-4 relative z-10 text-center max-w-5xl">
<div className="inline-block mb-6 px-4 py-1.5 bg-primary/10 border border-primary/20 rounded-full">
<p className="text-sm font-mono text-primary">August 24-29, 2025 Fifth Edition</p>
</div>
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold mb-6 text-balance leading-[1.1]"> <h1 className="text-5xl md:text-7xl lg:text-8xl font-bold mb-6 text-balance leading-[1.1]">
Crypto Commons Crypto Commons
<br /> <br />
Gathering Gathering
</h1> </h1>
<div className="inline-block mb-6 px-4 py-1.5 bg-primary/10 border border-primary/20 rounded-full backdrop-blur-sm">
<p className="text-sm font-mono text-primary">August 24-28, 2026</p>
</div>
<p className="text-lg md:text-xl text-muted-foreground mb-8 max-w-2xl mx-auto text-pretty leading-relaxed"> <p className="text-lg md:text-xl text-muted-foreground mb-8 max-w-2xl mx-auto text-pretty leading-relaxed">
A hack-ademic confluence of commons praxis and the latest cryptographic technologies, set in the foothills A hack-ademic confluence of commons praxis and the latest cryptographic technologies, set in the foothills
of the Austrian Alps. of the Austrian Alps.
@ -36,15 +34,11 @@ export default function HomePage() {
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center mb-12"> <div className="flex flex-col sm:flex-row gap-4 justify-center items-center mb-12">
<Button size="lg" className="gap-2 text-base" asChild> <Button size="lg" className="gap-2 text-base" asChild>
<Link <Link href="/register">
href="https://docs.google.com/forms/d/1crj5ukURBHMKhVg71cDbikjL2D8BVtNw9C6gl3myG9c/edit"
target="_blank"
rel="noopener noreferrer"
>
Register Now <ArrowRight className="w-4 h-4" /> Register Now <ArrowRight className="w-4 h-4" />
</Link> </Link>
</Button> </Button>
<Button size="lg" variant="outline" className="gap-2 text-base bg-transparent" asChild> <Button size="lg" variant="outline" className="gap-2 text-base bg-transparent backdrop-blur-sm" asChild>
<Link href="https://t.me/+8a7PooNV6202YjI0" target="_blank" rel="noopener noreferrer"> <Link href="https://t.me/+8a7PooNV6202YjI0" target="_blank" rel="noopener noreferrer">
Join Telegram Join Telegram
</Link> </Link>
@ -62,7 +56,28 @@ export default function HomePage() {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Users className="w-4 h-4 text-primary" /> <Users className="w-4 h-4 text-primary" />
<span>Activists, Researchers, Hackers, Builders</span> <span>Hackers, Academics, Activists, Researchers, Builders </span>
</div>
</div>
</div>
</section>
{/* Community Photos Section */}
<section className="py-16 px-4 bg-background">
<div className="container mx-auto max-w-6xl">
<div className="grid md:grid-cols-3 gap-4">
<div className="aspect-[4/3] rounded-lg overflow-hidden">
<img
src="/images/image.jpeg"
alt="CCG participants in discussion circle"
className="w-full h-full object-cover"
/>
</div>
<div className="aspect-[4/3] rounded-lg overflow-hidden">
<img src="/images/image.jpeg" alt="Community meal at CCG" className="w-full h-full object-cover" />
</div>
<div className="aspect-[4/3] rounded-lg overflow-hidden">
<img src="/images/image.png" alt="Hands-on hacking at CCG" className="w-full h-full object-cover" />
</div> </div>
</div> </div>
</div> </div>
@ -86,7 +101,7 @@ export default function HomePage() {
corporate control. corporate control.
</p> </p>
<p className="text-muted-foreground leading-relaxed"> <p className="text-muted-foreground leading-relaxed">
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. reflection for a growing community of crypto-commons thinkers from around the world.
</p> </p>
</div> </div>
@ -165,20 +180,29 @@ export default function HomePage() {
</section> </section>
{/* Unconference Format */} {/* Unconference Format */}
<section className="py-24 px-4 bg-primary text-primary-foreground"> <section className="py-24 px-4 relative overflow-hidden">
<div className="container mx-auto max-w-4xl text-center"> <div
<h2 className="text-3xl md:text-4xl font-bold mb-6 text-balance">What is an Unconference?</h2> className="absolute inset-0 bg-cover bg-center"
<p className="text-lg leading-relaxed opacity-90 mb-8"> style={{
backgroundImage: `url('/images/image.jpeg')`,
}}
/>
<div className="absolute inset-0 bg-primary/90" />
<div className="container mx-auto max-w-4xl text-center relative z-10">
<h2 className="text-3xl md:text-4xl font-bold mb-6 text-balance text-primary-foreground">
What is an Unconference?
</h2>
<p className="text-lg leading-relaxed opacity-90 mb-8 text-primary-foreground">
An unconference is a participant-driven event format that emphasizes open, flexible, and spontaneous An unconference is a participant-driven event format that emphasizes open, flexible, and spontaneous
discussions rather than traditional pre-planned presentations. Unlike conventional conferences, discussions rather than traditional pre-planned presentations. Unlike conventional conferences,
unconferences have no predefined agenda or speakers. unconferences have no predefined agenda or speakers.
</p> </p>
<p className="leading-relaxed opacity-90 mb-8"> <p className="leading-relaxed opacity-90 mb-8 text-primary-foreground">
Attendees collaboratively propose topics, sessions, and activities on the spot. Participants vote or 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 self-organize around topics they find most interesting, forming small groups or breakout sessions where
discussions, workshops, or hands-on activities take place. discussions, workshops, or hands-on activities take place.
</p> </p>
<p className="leading-relaxed opacity-90"> <p className="leading-relaxed opacity-90 text-primary-foreground">
Everyone is considered equally qualified to contribute, share insights, and guide conversations. Bring your 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. ideas for talks, roundtables, workshops, prototyping sessions, board game nights, LARPs, and more.
</p> </p>
@ -240,26 +264,20 @@ export default function HomePage() {
{/* CTA Section */} {/* CTA Section */}
<section className="py-24 px-4 bg-muted/30"> <section className="py-24 px-4 bg-muted/30">
<div className="container mx-auto max-w-3xl text-center"> <div className="container mx-auto max-w-3xl text-center">
<h2 className="text-3xl md:text-4xl font-bold mb-6 text-balance">Join the Fifth Edition of CCG</h2> <h2 className="text-3xl md:text-4xl font-bold mb-6 text-balance">Join the Sixth Edition of CCG</h2>
<p className="text-lg text-muted-foreground mb-8 text-pretty"> <p className="text-lg text-muted-foreground mb-8 text-pretty">
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. stock of what crypto commons has become and what it still could be.
</p> </p>
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-8"> <div className="flex flex-col sm:flex-row gap-4 justify-center mb-8">
<Button size="lg" className="gap-2" asChild> <Button size="lg" className="gap-2" asChild>
<Link <Link href="/register">
href="https://docs.google.com/forms/d/1crj5ukURBHMKhVg71cDbikjL2D8BVtNw9C6gl3myG9c/edit"
target="_blank"
rel="noopener noreferrer"
>
Register and Get Tickets <ArrowRight className="w-4 h-4" /> Register and Get Tickets <ArrowRight className="w-4 h-4" />
</Link> </Link>
</Button> </Button>
<Button size="lg" variant="outline" asChild> <Button size="lg" variant="outline" asChild>
<Link href="https://ccg2025.vercel.app/financial-transparency" target="_blank" rel="noopener noreferrer"> <Link href="/transparency">Financial Transparency</Link>
Financial Transparency
</Link>
</Button> </Button>
</div> </div>
@ -274,11 +292,11 @@ export default function HomePage() {
<div className="container mx-auto max-w-6xl"> <div className="container mx-auto max-w-6xl">
<div className="grid sm:grid-cols-2 md:grid-cols-4 gap-8 mb-8"> <div className="grid sm:grid-cols-2 md:grid-cols-4 gap-8 mb-8">
<div> <div>
<h3 className="font-bold mb-4">CCG 2025</h3> <h3 className="font-bold mb-4">CCG 2026</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Crypto Commons Gathering Crypto Commons Gathering
<br /> <br />
August 24-29, 2025 August 2026
</p> </p>
</div> </div>
@ -290,22 +308,21 @@ export default function HomePage() {
href="https://ccg2025.vercel.app/about" href="https://ccg2025.vercel.app/about"
className="text-muted-foreground hover:text-foreground transition-colors" className="text-muted-foreground hover:text-foreground transition-colors"
> >
About CCG 2025 About CCG 2026
</Link> </Link>
</li> </li>
<li> <li>
<Link <Link
href="https://ccg2025.vercel.app/directions" href="https://ccg2025.vercel.app/directions"
className="text-muted-foreground hover:text-foreground transition-colors" className="text-muted-foreground hover:text-foreground transition-colors"
target="_blank"
rel="noopener noreferrer"
> >
Directions Directions
</Link> </Link>
</li> </li>
<li> <li>
<Link <Link href="/transparency" className="text-muted-foreground hover:text-foreground transition-colors">
href="https://ccg2025.vercel.app/financial-transparency"
className="text-muted-foreground hover:text-foreground transition-colors"
>
Financial Transparency Financial Transparency
</Link> </Link>
</li> </li>
@ -355,7 +372,7 @@ export default function HomePage() {
</div> </div>
<div className="pt-8 border-t border-border text-center text-sm text-muted-foreground"> <div className="pt-8 border-t border-border text-center text-sm text-muted-foreground">
<p>© 2025 Crypto Commons Gathering. Built with solidarity for the commons.</p> <p>© 2026 Crypto Commons Gathering. Built with solidarity for the commons.</p>
</div> </div>
</div> </div>
</footer> </footer>

375
app/register/page.tsx Normal file
View File

@ -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 (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="border-b">
<div className="container mx-auto px-4 py-4">
<Link href="/" className="text-2xl font-bold text-primary">
CCG
</Link>
</div>
</header>
<main className="container mx-auto px-4 py-12 max-w-5xl">
<div className="text-center mb-12">
<h1 className="text-4xl md:text-5xl font-bold mb-4">Complete Your Registration</h1>
<p className="text-xl text-muted-foreground">Choose your payment method</p>
</div>
<Card className="mb-8 border-primary/40">
<CardHeader>
<CardTitle>Event Cost Breakdown</CardTitle>
<CardDescription>Full 6-day event costs (August 16-22, 2026)</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="flex justify-between items-center py-3 border-b border-border">
<div>
<div className="font-medium">Ticket Price</div>
<div className="text-sm text-muted-foreground">Venue rental & infrastructure</div>
</div>
<span className="text-lg font-semibold">200</span>
</div>
<div className="flex justify-between items-center py-3 border-b border-border">
<div>
<div className="font-medium">Accommodation</div>
<div className="text-sm text-muted-foreground">6 nights dorm (37.90/night)</div>
</div>
<span className="text-lg font-semibold">227.40</span>
</div>
<div className="flex justify-between items-center py-3 border-b border-border">
<div>
<div className="font-medium">Food</div>
<div className="text-sm text-muted-foreground">6 days meals (22.50/day avg)</div>
</div>
<span className="text-lg font-semibold">135</span>
</div>
<div className="flex justify-between items-center py-4 bg-primary/10 -mx-6 px-6 mt-4">
<div>
<div className="font-bold text-lg">Total Estimated Cost</div>
<div className="text-sm text-muted-foreground">Pay ticket now, accommodation & food at venue</div>
</div>
<span className="text-2xl font-bold text-primary">562.40</span>
</div>
</div>
<p className="text-sm text-muted-foreground mt-4">
You're only paying the ticket price (200) now. Accommodation and food will be arranged and paid
separately at the Commons Hub.
</p>
</CardContent>
</Card>
{/* </CHANGE> */}
{/* Payment Methods */}
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Payment Options</CardTitle>
<CardDescription>Ticket Price: 200 per person</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<form action="/api/create-checkout-session" method="POST">
<input type="hidden" name="registrationData" value={JSON.stringify(formData)} />
<div className="space-y-4">
<div>
<Label className="text-base font-semibold mb-3 block">Select Payment Method</Label>
<RadioGroup name="paymentMethod" defaultValue="card" className="space-y-3">
<div className="flex items-center space-x-2 border rounded-lg p-4 hover:border-primary transition-colors">
<RadioGroupItem value="card" id="card" />
<Label htmlFor="card" className="flex-1 cursor-pointer">
<div className="font-medium">Credit Card</div>
<div className="text-sm text-muted-foreground">Visa, Mastercard, Amex</div>
</Label>
</div>
<div className="flex items-center space-x-2 border rounded-lg p-4 hover:border-primary transition-colors">
<RadioGroupItem value="sepa_debit" id="sepa" />
<Label htmlFor="sepa" className="flex-1 cursor-pointer">
<div className="font-medium">SEPA Bank Transfer</div>
<div className="text-sm text-muted-foreground">Direct debit from European banks</div>
</Label>
</div>
<div className="flex items-center space-x-2 border rounded-lg p-4 hover:border-primary transition-colors border-primary/30">
<RadioGroupItem value="crypto" id="crypto" />
<Label htmlFor="crypto" className="flex-1 cursor-pointer">
<div className="font-medium">Cryptocurrency</div>
<div className="text-sm text-muted-foreground">USDC Stablecoin (settles as EUR)</div>
</Label>
</div>
</RadioGroup>
</div>
<div className="flex gap-3 pt-4">
<Button type="button" variant="outline" onClick={() => setStep("form")} className="flex-1">
Back to Form
</Button>
<Button type="submit" className="flex-1">
Proceed to Payment
</Button>
</div>
</div>
</form>
</CardContent>
</Card>
<div className="text-center text-sm text-muted-foreground">
<p>
All payments are processed securely through Stripe. You'll receive a confirmation email after successful
payment.
</p>
</div>
</div>
</main>
</div>
)
}
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="border-b">
<div className="container mx-auto px-4 py-4">
<Link href="/" className="text-2xl font-bold text-primary">
CCG
</Link>
</div>
</header>
<main className="container mx-auto px-4 py-12 max-w-3xl">
<div className="text-center mb-12">
<h1 className="text-4xl md:text-5xl font-bold mb-4">Register for CCG 2026</h1>
<p className="text-xl text-muted-foreground">August 16-22, 2026 at the Commons Hub in Austria</p>
</div>
<Card>
<CardHeader>
<CardTitle>Registration Form</CardTitle>
<CardDescription>Tell us about yourself and what you'd like to bring to CCG</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Name */}
<div className="space-y-2">
<Label htmlFor="name">What's your name? *</Label>
<Input
id="name"
required
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
/>
</div>
{/* Contact */}
<div className="space-y-2">
<Label htmlFor="contact">How can we contact you besides via email? *</Label>
<Input
id="contact"
required
placeholder="Telegram, Signal, phone, etc."
value={formData.contact}
onChange={(e) => setFormData({ ...formData, contact: e.target.value })}
/>
</div>
{/* Contributions */}
<div className="space-y-2">
<Label htmlFor="contributions">What inputs do you want to contribute to CCG? *</Label>
<Textarea
id="contributions"
required
placeholder="This could be a talk, workshop, research inquiry, prototype, game, performance, etc."
className="min-h-[120px]"
value={formData.contributions}
onChange={(e) => setFormData({ ...formData, contributions: e.target.value })}
/>
<p className="text-sm text-muted-foreground">
This event is organized as an unconference, meaning that each participant is invited to co-create the
program.
</p>
</div>
{/* Expectations */}
<div className="space-y-2">
<Label htmlFor="expectations">What do you expect to gain from participating? *</Label>
<Textarea
id="expectations"
required
placeholder="What are you looking for in particular?"
className="min-h-[100px]"
value={formData.expectations}
onChange={(e) => setFormData({ ...formData, expectations: e.target.value })}
/>
</div>
{/* How heard */}
<div className="space-y-2">
<Label htmlFor="howHeard">How did you hear about CCG?</Label>
<Input
id="howHeard"
placeholder="First timers: Did anyone recommend it to you? (they might be rewarded!)"
value={formData.howHeard}
onChange={(e) => setFormData({ ...formData, howHeard: e.target.value })}
/>
</div>
{/* Dietary Requirements */}
<div className="space-y-3">
<Label>Dietary Requirements</Label>
<p className="text-sm text-muted-foreground">
Food will involve catering as well as self-prepared meals. Do you have any special dietary
requirements?
</p>
<div className="space-y-2">
{["vegetarian", "vegan", "gluten free", "lactose free"].map((diet) => (
<div key={diet} className="flex items-center space-x-2">
<Checkbox
id={diet}
checked={formData.dietary.includes(diet)}
onCheckedChange={(checked) => handleDietaryChange(diet, checked as boolean)}
/>
<Label htmlFor={diet} className="font-normal cursor-pointer">
{diet.charAt(0).toUpperCase() + diet.slice(1)}
</Label>
</div>
))}
<div className="flex items-start space-x-2 mt-2">
<Checkbox
id="other"
checked={!!formData.dietaryOther}
onCheckedChange={(checked) => {
if (!checked) setFormData({ ...formData, dietaryOther: "" })
}}
/>
<div className="flex-1">
<Label htmlFor="other" className="font-normal cursor-pointer">
Other:
</Label>
<Input
id="dietaryOther"
className="mt-1"
placeholder="Please specify..."
value={formData.dietaryOther}
onChange={(e) => setFormData({ ...formData, dietaryOther: e.target.value })}
/>
</div>
</div>
</div>
</div>
{/* Commons Crews Consent */}
<div className="space-y-3">
<Label>Commons Crews Participation *</Label>
<div className="bg-muted p-4 rounded-lg space-y-2 text-sm">
<p>
Part of the magic of CCG is that we consider the event a 'temporary commons' - we co-produce its
program as well as its leisure activities collectively.
</p>
<p>
This also involves joint care for the space and its maintenance needs, for emerging social and
emotional dynamics and for documentation of the sessions.
</p>
<p className="font-medium">
Besides active involvement in shaping the content of the event, you will be expected to contribute
to one or more 'commons crews' (kitchen, cleaning, documentation, facilitation, fire/water and
atmosphere).
</p>
</div>
<RadioGroup
required
value={formData.crewConsent}
onValueChange={(value) => setFormData({ ...formData, crewConsent: value })}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="cant-wait" id="cant-wait" />
<Label htmlFor="cant-wait" className="font-normal cursor-pointer">
Can't wait
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="yes-please" id="yes-please" />
<Label htmlFor="yes-please" className="font-normal cursor-pointer">
Yes please gimme work!
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="love-it" id="love-it" />
<Label htmlFor="love-it" className="font-normal cursor-pointer">
I love getting my hands dirty :D
</Label>
</div>
</RadioGroup>
</div>
<div className="pt-4">
<Button type="submit" className="w-full" size="lg">
Continue to Payment
</Button>
</div>
<div className="text-sm text-muted-foreground text-center">
<p>
Questions? Contact us on{" "}
<a href="https://t.me/+Bc48A-mqvmY3MmE0" className="text-primary hover:underline">
Telegram
</a>
</p>
</div>
</form>
</CardContent>
</Card>
</main>
</div>
)
}

42
app/success/page.tsx Normal file
View File

@ -0,0 +1,42 @@
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { CheckCircle2 } from "lucide-react"
import Link from "next/link"
export default function SuccessPage() {
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
<Card className="max-w-md w-full">
<CardHeader className="text-center">
<div className="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center mx-auto mb-4">
<CheckCircle2 className="w-10 h-10 text-primary" />
</div>
<CardTitle className="text-2xl">Payment Successful!</CardTitle>
<CardDescription>Welcome to Crypto Commons Gathering 2025</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-center text-muted-foreground">
You've successfully registered for CCG 2025. Check your email for confirmation details and next steps.
</p>
<div className="space-y-2">
<h3 className="font-semibold">What's Next?</h3>
<ul className="text-sm space-y-1 text-muted-foreground">
<li> Join our Telegram community</li>
<li> Watch for pre-event communications</li>
<li> Prepare your session proposals</li>
<li> Get ready for an amazing experience</li>
</ul>
</div>
<div className="flex gap-3 pt-4">
<Button asChild className="flex-1">
<Link href="/">Back to Home</Link>
</Button>
<Button asChild variant="outline" className="flex-1 bg-transparent">
<a href="https://t.me/+Bc48A-mqvmY3MmE0">Join Telegram</a>
</Button>
</div>
</CardContent>
</Card>
</div>
)
}

220
app/transparency/page.tsx Normal file
View File

@ -0,0 +1,220 @@
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { CheckCircle2, Mail } from "lucide-react"
import Link from "next/link"
export default function FinancialTransparencyPage() {
return (
<div className="min-h-screen">
{/* Header */}
<header className="border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50">
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
<Link href="/" className="text-xl font-bold">
CCG 2026
</Link>
<nav className="flex items-center gap-4">
<Button variant="ghost" size="sm" asChild>
<Link href="/">Home</Link>
</Button>
<Button size="sm" asChild>
<Link href="/register">Register</Link>
</Button>
</nav>
</div>
</header>
{/* Hero Section */}
<section className="py-16 px-4 bg-muted/30">
<div className="container mx-auto max-w-4xl text-center">
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-balance">Financial Transparency</h1>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto text-pretty">
We believe in complete transparency about the costs and financial structure of CCG 2026. Here's a detailed
breakdown of all expenses and what your contribution covers.
</p>
</div>
</section>
{/* Main Content */}
<section className="py-16 px-4">
<div className="container mx-auto max-w-4xl">
{/* Ticket Price */}
<Card className="mb-8">
<CardHeader>
<CardTitle className="text-2xl">Ticket Price</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-baseline gap-4">
<span className="text-4xl font-bold text-primary">200</span>
<span className="text-muted-foreground">per person</span>
</div>
<p className="text-muted-foreground leading-relaxed">
The ticket price is set to cover the venue rental and basic infrastructure, based on expected
attendance. No one from the organizing team is being paid from ticket sales, and all are covering their
own travel, food and accommodation, until more sustainable structures and future sponsorships are in
place.
</p>
<div className="pt-4">
<Button asChild>
<Link href="/register">Get Your Ticket</Link>
</Button>
</div>
</CardContent>
</Card>
{/* Accommodation */}
<Card className="mb-8">
<CardHeader>
<CardTitle className="text-2xl">Accommodation</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground mb-4">
Note that accommodation is not included in the ticket price. These are provided by the Commons Hub:
</p>
<div className="bg-muted/50 p-6 rounded-lg">
<div className="flex items-baseline gap-3 mb-2">
<span className="text-3xl font-bold">37.90</span>
<span className="text-muted-foreground">per night</span>
</div>
<p className="text-sm text-muted-foreground">Dorm accommodation</p>
</div>
<p className="text-muted-foreground leading-relaxed">
Accommodation includes shared dormitory-style rooms with basic amenities. Bedding and towels are
provided.
</p>
</CardContent>
</Card>
{/* Food */}
<Card className="mb-8">
<CardHeader>
<CardTitle className="text-2xl">Food</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground mb-4">
Food is also not included in the ticket price and is provided by the Commons Hub:
</p>
<div className="bg-muted/50 p-6 rounded-lg mb-4">
<div className="flex items-baseline gap-3 mb-2">
<span className="text-3xl font-bold">22.50</span>
<span className="text-muted-foreground">per day average</span>
</div>
</div>
<div className="space-y-4">
<div>
<h4 className="font-semibold mb-2">Breakdown:</h4>
<ul className="space-y-2">
<li className="flex items-start gap-2">
<CheckCircle2 className="w-5 h-5 text-primary mt-0.5 flex-shrink-0" />
<span className="text-muted-foreground">
<strong>First 3 days:</strong> fully catered at 35/day
</span>
</li>
<li className="flex items-start gap-2">
<CheckCircle2 className="w-5 h-5 text-primary mt-0.5 flex-shrink-0" />
<span className="text-muted-foreground">
<strong>Last 3 days:</strong> self-organized by participants and the Crypto Commons Association
at ~10/day
</span>
</li>
</ul>
</div>
<p className="text-muted-foreground leading-relaxed">
Meals include breakfast, lunch, and dinner with vegetarian and vegan options available. The
self-organized days involve communal cooking and shared meal preparation.
</p>
</div>
</CardContent>
</Card>
{/* Total Cost Estimate */}
<Card className="mb-8 border-primary/40">
<CardHeader>
<CardTitle className="text-2xl">Total Cost Estimate</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-muted-foreground mb-6">
For the full 6-day event (August 2026), here's what you can expect to pay:
</p>
<div className="space-y-3">
<div className="flex justify-between items-center py-3 border-b border-border">
<span className="font-medium">Ticket</span>
<span className="text-lg font-semibold">200</span>
</div>
<div className="flex justify-between items-center py-3 border-b border-border">
<span className="font-medium">Accommodation (6 nights)</span>
<span className="text-lg font-semibold">227.40</span>
</div>
<div className="flex justify-between items-center py-3 border-b border-border">
<span className="font-medium">Food (6 days)</span>
<span className="text-lg font-semibold">135</span>
</div>
<div className="flex justify-between items-center py-4 bg-primary/10 -mx-6 px-6 mt-4">
<span className="font-bold text-lg">Total</span>
<span className="text-2xl font-bold text-primary">562.40</span>
</div>
</div>
<p className="text-sm text-muted-foreground italic pt-4">
*This is an estimate. Final costs may vary slightly based on actual meal arrangements and accommodation
choices.
</p>
</CardContent>
</Card>
{/* Financial Support */}
<Card className="bg-accent/20 border-accent">
<CardHeader>
<CardTitle className="text-2xl">Need Financial Support?</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-muted-foreground leading-relaxed">
We know these costs add up, especially with travel. If the price is a barrier, please don't hesitate to
reach out at{" "}
<a href="mailto:cryptocommonsgathering@gmail.com" className="text-primary hover:underline font-medium">
cryptocommonsgathering@gmail.com
</a>
. We'll do our best to find a solution together.
</p>
<p className="text-muted-foreground leading-relaxed">
We believe that financial constraints should not prevent anyone from participating in building
commons-oriented futures. We're committed to working with participants to find creative solutions for
those who need support.
</p>
<div className="pt-4">
<Button variant="outline" className="gap-2 bg-transparent" asChild>
<a href="mailto:cryptocommonsgathering@gmail.com">
<Mail className="w-4 h-4" />
Contact Us About Support
</a>
</Button>
</div>
</CardContent>
</Card>
</div>
</section>
{/* CTA Section */}
<section className="py-16 px-4 bg-primary text-primary-foreground">
<div className="container mx-auto max-w-3xl text-center">
<h2 className="text-3xl font-bold mb-6">Ready to Join CCG 2026?</h2>
<p className="text-lg mb-8 opacity-90">
Register now to be part of the sixth edition of this transformative gathering.
</p>
<Button size="lg" variant="secondary" asChild>
<Link href="/register">Register Now</Link>
</Button>
</div>
</section>
{/* Footer */}
<footer className="py-12 px-4 border-t border-border">
<div className="container mx-auto max-w-6xl text-center text-sm text-muted-foreground">
<p>© 2026 Crypto Commons Gathering. Built with solidarity for the commons.</p>
</div>
</footer>
</div>
)
}

View File

@ -0,0 +1,32 @@
'use client'
import * as React from 'react'
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import { CheckIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
'peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }

21
components/ui/input.tsx Normal file
View File

@ -0,0 +1,21 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
return (
<input
type={type}
data-slot="input"
className={cn(
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className,
)}
{...props}
/>
)
}
export { Input }

24
components/ui/label.tsx Normal file
View File

@ -0,0 +1,24 @@
'use client'
import * as React from 'react'
import * as LabelPrimitive from '@radix-ui/react-label'
import { cn } from '@/lib/utils'
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
className,
)}
{...props}
/>
)
}
export { Label }

View File

@ -0,0 +1,45 @@
'use client'
import * as React from 'react'
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
import { CircleIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn('grid gap-3', className)}
{...props}
/>
)
}
function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
'border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator
data-slot="radio-group-indicator"
className="relative flex items-center justify-center"
>
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
}
export { RadioGroup, RadioGroupItem }

View File

@ -0,0 +1,18 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
return (
<textarea
data-slot="textarea"
className={cn(
'border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className,
)}
{...props}
/>
)
}
export { Textarea }

View File

@ -14,7 +14,7 @@
"@radix-ui/react-alert-dialog": "1.1.4", "@radix-ui/react-alert-dialog": "1.1.4",
"@radix-ui/react-aspect-ratio": "1.1.1", "@radix-ui/react-aspect-ratio": "1.1.1",
"@radix-ui/react-avatar": "1.1.2", "@radix-ui/react-avatar": "1.1.2",
"@radix-ui/react-checkbox": "1.1.3", "@radix-ui/react-checkbox": "latest",
"@radix-ui/react-collapsible": "1.1.2", "@radix-ui/react-collapsible": "1.1.2",
"@radix-ui/react-context-menu": "2.2.4", "@radix-ui/react-context-menu": "2.2.4",
"@radix-ui/react-dialog": "1.1.4", "@radix-ui/react-dialog": "1.1.4",
@ -25,7 +25,7 @@
"@radix-ui/react-navigation-menu": "1.2.3", "@radix-ui/react-navigation-menu": "1.2.3",
"@radix-ui/react-popover": "1.1.4", "@radix-ui/react-popover": "1.1.4",
"@radix-ui/react-progress": "1.1.1", "@radix-ui/react-progress": "1.1.1",
"@radix-ui/react-radio-group": "1.2.2", "@radix-ui/react-radio-group": "latest",
"@radix-ui/react-scroll-area": "1.2.2", "@radix-ui/react-scroll-area": "1.2.2",
"@radix-ui/react-select": "2.1.4", "@radix-ui/react-select": "2.1.4",
"@radix-ui/react-separator": "1.1.1", "@radix-ui/react-separator": "1.1.1",
@ -55,6 +55,7 @@
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"recharts": "2.15.4", "recharts": "2.15.4",
"sonner": "^1.7.4", "sonner": "^1.7.4",
"stripe": "latest",
"tailwind-merge": "^2.5.5", "tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2", "vaul": "^1.1.2",

File diff suppressed because it is too large Load Diff

BIN
public/images/image.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 KiB

BIN
public/images/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB