add tickets page components

This commit is contained in:
Alisa Cantillo-Olsson 2026-04-22 16:18:26 +02:00
parent 25b5d30596
commit 3ed08a71a6
6 changed files with 331 additions and 12 deletions

View File

@ -2,6 +2,7 @@ import { Routes, Route } from "react-router-dom";
import Home from "./pages/Home"; import Home from "./pages/Home";
import Programm from "./pages/Programm"; import Programm from "./pages/Programm";
import Speaker from "./pages/Speaker"; import Speaker from "./pages/Speaker";
import Tickets from "./pages/Tickets";
function App() { function App() {
return ( return (
@ -9,6 +10,7 @@ function App() {
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/programm" element={<Programm />} /> <Route path="/programm" element={<Programm />} />
<Route path="/speaker" element={<Speaker />} /> <Route path="/speaker" element={<Speaker />} />
<Route path="/tickets" element={<Tickets />} />
</Routes> </Routes>
); );
} }

View File

@ -11,7 +11,7 @@ type Props = {
export type Header23Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>; export type Header23Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
export const Header23 = (props: Header23Props) => { export const Header23 = (props: Header23Props) => {
const { heading, description, buttons } = { const { heading, description, buttons, children } = {
...Header23Defaults, ...Header23Defaults,
...props, ...props,
}; };
@ -44,6 +44,7 @@ export const Header23 = (props: Header23Props) => {
))} ))}
</div> </div>
)} )}
{children}
</div> </div>
</div> </div>
</section> </section>

View File

@ -4,6 +4,9 @@ import { Button, useMediaQuery } from "@relume_io/relume-ui";
import type { ButtonProps } from "@relume_io/relume-ui"; import type { ButtonProps } from "@relume_io/relume-ui";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { RxChevronDown } from "react-icons/rx"; import { RxChevronDown } from "react-icons/rx";
import { Link } from "react-router-dom";
type NavButtonProps = ButtonProps & { url?: string };
type ImageProps = { type ImageProps = {
url?: string; url?: string;
@ -20,7 +23,7 @@ type NavLink = {
type Props = { type Props = {
logo: ImageProps; logo: ImageProps;
navLinks: NavLink[]; navLinks: NavLink[];
buttons: ButtonProps[]; buttons: NavButtonProps[];
}; };
export type Navbar3Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>; export type Navbar3Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
@ -89,11 +92,15 @@ export const Navbar3 = (props: Navbar3Props) => {
</div> </div>
))} ))}
<div className="mt-6 lg:hidden"> <div className="mt-6 lg:hidden">
{buttons.map((button, index) => ( {buttons.map(({ url, ...button }, index) =>
<Button key={index} {...button} className="w-full"> url ? (
{button.title} <Link key={index} to={url}>
</Button> <Button {...button} className="w-full">{button.title}</Button>
))} </Link>
) : (
<Button key={index} {...button} className="w-full">{button.title}</Button>
)
)}
</div> </div>
</motion.div> </motion.div>
{isMobileMenuOpen && ( {isMobileMenuOpen && (
@ -112,11 +119,19 @@ export const Navbar3 = (props: Navbar3Props) => {
</a> </a>
<div className="flex min-h-16 items-center justify-end gap-x-4"> <div className="flex min-h-16 items-center justify-end gap-x-4">
<div> <div>
{buttons.map((button, index) => ( {buttons.map(({ url, ...button }, index) =>
url ? (
<Link key={index} to={url}>
<Button {...button} className="px-5 py-2 bg-electric-violet text-cloud-white rounded-2xl border-electric-violet font-barlow font-semibold">
{button.title}
</Button>
</Link>
) : (
<Button key={index} {...button} className="px-5 py-2 bg-electric-violet text-cloud-white rounded-2xl border-electric-violet font-barlow font-semibold"> <Button key={index} {...button} className="px-5 py-2 bg-electric-violet text-cloud-white rounded-2xl border-electric-violet font-barlow font-semibold">
{button.title} {button.title}
</Button> </Button>
))} )
)}
</div> </div>
</div> </div>
</section> </section>
@ -201,6 +216,7 @@ export const Navbar3Defaults: Props = {
title: "Tickets", title: "Tickets",
size: "sm", size: "sm",
variant: "primary", variant: "primary",
url: "/tickets",
}, },
], ],
}; };

View File

@ -0,0 +1,79 @@
import React, { useState } from "react";
type TicketPlan = {
period: string;
price: number;
date: string;
};
type Props = {
heading: string;
plan: TicketPlan;
};
export type Pricing1TicketProps = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
const TicketCardViolet = ({ plan }: { plan: TicketPlan }) => {
const [qty, setQty] = useState(0);
const total = qty * plan.price;
return (
<div className="bg-electric-violet text-cloud-white p-6 md:p-8 flex flex-col gap-5 font-barlow">
<p className="text-sm font-semibold">{plan.period}</p>
<h3 className="text-6xl font-bold md:text-8xl">CHF {plan.price}</h3>
<p className="text-sm">{plan.date}</p>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<button
onClick={() => setQty((q) => Math.max(0, q - 1))}
className="w-8 h-8 rounded-full bg-acid-lime text-tech-navy flex items-center justify-center font-bold text-lg leading-none"
>
</button>
<span className="w-6 text-center font-semibold">{qty}</span>
<button
onClick={() => setQty((q) => q + 1)}
className="w-8 h-8 rounded-full bg-acid-lime text-tech-navy flex items-center justify-center font-bold text-lg leading-none"
>
+
</button>
</div>
<div className="text-right text-sm font-semibold">
<div>CHF {plan.price},-</div>
<div>{total},-</div>
</div>
</div>
<button className="w-full bg-acid-lime text-tech-navy font-semibold py-3 text-sm">
Ticket sichern
</button>
</div>
);
};
export const Pricing1Ticket = (props: Pricing1TicketProps) => {
const { heading, plan } = { ...Pricing1TicketDefaults, ...props };
return (
<section className="px-[5%] py-16 md:py-24 bg-neutral-dark font-barlow">
<div className="container">
<h2 className="mb-10 text-center text-4xl font-bold text-cloud-white md:mb-14 md:text-5xl">
{heading}
</h2>
<div className="mx-auto w-full max-w-sm">
<TicketCardViolet plan={plan} />
</div>
</div>
</section>
);
};
export const Pricing1TicketDefaults: Props = {
heading: "Earlybird",
plan: {
period: "DO FR",
price: 250,
date: "25. 26. Juni 2026",
},
};
export default Pricing1Ticket;

View File

@ -0,0 +1,88 @@
import React, { useState } from "react";
type TicketPlan = {
day: string;
price: number;
date: string;
};
type Props = {
heading: string;
plans: TicketPlan[];
};
export type Pricing39TicketProps = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
const TicketCardLime = ({ plan }: { plan: TicketPlan }) => {
const [qty, setQty] = useState(0);
const total = qty * plan.price;
return (
<div className="bg-acid-lime text-tech-navy p-6 md:p-8 flex flex-col gap-5 font-barlow">
<p className="text-sm font-semibold">{plan.day}</p>
<h3 className="text-6xl font-bold md:text-8xl">CHF {plan.price}</h3>
<p className="text-sm">{plan.date}</p>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<button
onClick={() => setQty((q) => Math.max(0, q - 1))}
className="w-8 h-8 rounded-full bg-electric-violet text-cloud-white flex items-center justify-center font-bold text-lg leading-none"
>
</button>
<span className="w-6 text-center font-semibold">{qty}</span>
<button
onClick={() => setQty((q) => q + 1)}
className="w-8 h-8 rounded-full bg-electric-violet text-cloud-white flex items-center justify-center font-bold text-lg leading-none"
>
+
</button>
</div>
<div className="text-right text-sm font-semibold">
<div>CHF {plan.price},-</div>
<div>{total},-</div>
</div>
</div>
<button className="w-full bg-electric-violet text-cloud-white font-semibold py-3 text-sm">
Ticket sichern
</button>
</div>
);
};
export const Pricing39Ticket = (props: Pricing39TicketProps) => {
const { heading, plans } = { ...Pricing39TicketDefaults, ...props };
return (
<section className="px-[5%] py-16 md:py-24 bg-white font-barlow">
<div className="container">
<h2 className="mb-10 text-4xl font-bold text-tech-navy md:mb-14 md:text-5xl">
{heading}
</h2>
<div
className={`grid gap-6 ${
plans.length === 1
? "grid-cols-1 max-w-sm"
: plans.length === 2
? "grid-cols-1 sm:grid-cols-2 max-w-2xl"
: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"
}`}
>
{plans.map((plan, index) => (
<TicketCardLime key={index} plan={plan} />
))}
</div>
</div>
</section>
);
};
export const Pricing39TicketDefaults: Props = {
heading: "1-Tagespässe",
plans: [
{ day: "Donnerstag", price: 131, date: "25. Juni 2026" },
{ day: "Freitag", price: 131, date: "26. Juni 2026" },
],
};
export default Pricing39Ticket;

View File

@ -0,0 +1,133 @@
"use client";
import React, { useState, useEffect } from "react";
import { Navbar3 } from "../components/Navbar";
import { Header23 } from "../components/Header23";
import { Pricing1Ticket } from "../components/Pricing1Ticket";
import { Pricing39Ticket } from "../components/Pricing39Ticket";
import { Footer3 } from "../components/Footer";
import { Button, Input } from "@relume_io/relume-ui";
const COUNTDOWN_TARGET = "2026-06-25T09:00:00.000+02:00";
type CountdownValues = { days: string; hours: string; minutes: string; seconds: string };
const Countdown = ({ targetDate }: { targetDate: string }) => {
const [time, setTime] = useState<CountdownValues>({
days: "00",
hours: "00",
minutes: "00",
seconds: "00",
});
useEffect(() => {
const target = new Date(targetDate).getTime();
const pad = (n: number) => (n < 10 ? `0${n}` : `${n}`);
const tick = () => {
const diff = target - Date.now();
if (diff <= 0) {
setTime({ days: "00", hours: "00", minutes: "00", seconds: "00" });
return;
}
setTime({
days: pad(Math.floor(diff / 86400000)),
hours: pad(Math.floor((diff % 86400000) / 3600000)),
minutes: pad(Math.floor((diff % 3600000) / 60000)),
seconds: pad(Math.floor((diff % 60000) / 1000)),
});
};
tick();
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [targetDate]);
const Cell = ({ value, label }: { value: string; label: string }) => (
<div className="flex min-w-[4.5rem] flex-col items-center">
<span className="text-4xl font-bold leading-tight text-cloud-white md:text-5xl lg:text-6xl">
{value}
</span>
<span className="text-sm text-cloud-white">{label}</span>
</div>
);
const Divider = () => <div className="hidden w-px self-stretch bg-white/30 sm:block" />;
return (
<div className="flex flex-wrap justify-center gap-4 border border-white/30 px-4 py-4 sm:flex-nowrap sm:px-6">
<Cell value={time.days} label="Days" />
<Divider />
<Cell value={time.hours} label="Hours" />
<Divider />
<Cell value={time.minutes} label="Mins" />
<Divider />
<Cell value={time.seconds} label="Secs" />
</div>
);
};
const Tickets = () => {
const [emailInput, setEmailInput] = useState("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log({ emailInput });
};
return (
<div>
<Navbar3 />
<Header23
heading="Ticketshop"
description="Erfahre von den klügsten Köpfen der Branche, wie sie komplexe Design-Herausforderungen meistern. Von Schweizer Präzision im Interface bis hin zu globalen Strategien führender Tech-Giganten unsere Speaker bringen handfeste Insights statt nur Buzzwords."
buttons={[]}
>
<div className="mt-5 font-semibold text-cloud-white md:mt-6 md:text-md">
DO FR, 25. 26. Juni 2026
</div>
<p className="mt-4 bg-acid-lime px-2 py-1 text-sm font-semibold text-tech-navy">
10 Spots left!
</p>
<div className="mt-8 w-full">
<Countdown targetDate={COUNTDOWN_TARGET} />
</div>
<div className="mt-6 w-full max-w-sm md:mt-8 mx-auto">
<form
className="mb-4 grid max-w-sm grid-cols-1 gap-y-3 sm:grid-cols-[1fr_max-content] sm:gap-4"
onSubmit={handleSubmit}
>
<Input
id="email"
type="email"
placeholder="Enter your email"
value={emailInput}
onChange={(e) => setEmailInput(e.target.value)}
className="text-tech-navy"
/>
<Button title="Get Updates" size="sm">Get Updates</Button>
</form>
<p className="text-xs text-cloud-white">
By clicking Get Updates you&apos;re confirming that you agree with our{" "}
<a href="#" className="underline">Terms and Conditions</a>.
</p>
</div>
</Header23>
<Pricing1Ticket />
<Pricing39Ticket
heading="1-Tagespässe"
plans={[
{ day: "Donnerstag", price: 131, date: "25. Juni 2026" },
{ day: "Freitag", price: 131, date: "26. Juni 2026" },
]}
/>
<Pricing39Ticket
heading="2-Tagespässe"
plans={[{ day: "Donnerstag Freitag", price: 236, date: "25. 26. Juni 2026" }]}
/>
<Footer3 />
</div>
);
};
export default Tickets;