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:
parent
82ff0f5516
commit
a3cf6995b2
|
|
@ -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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
122
app/globals.css
122
app/globals.css
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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: [
|
||||||
|
|
|
||||||
99
app/page.tsx
99
app/page.tsx
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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 }
|
||||||
|
|
@ -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 }
|
||||||
|
|
@ -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 }
|
||||||
|
|
@ -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 }
|
||||||
|
|
@ -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 }
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
6403
pnpm-lock.yaml
6403
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 503 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
Loading…
Reference in New Issue