add tickets page components
This commit is contained in:
parent
25b5d30596
commit
3ed08a71a6
@ -2,6 +2,7 @@ import { Routes, Route } from "react-router-dom";
|
||||
import Home from "./pages/Home";
|
||||
import Programm from "./pages/Programm";
|
||||
import Speaker from "./pages/Speaker";
|
||||
import Tickets from "./pages/Tickets";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@ -9,6 +10,7 @@ function App() {
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/programm" element={<Programm />} />
|
||||
<Route path="/speaker" element={<Speaker />} />
|
||||
<Route path="/tickets" element={<Tickets />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ type Props = {
|
||||
export type Header23Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
|
||||
|
||||
export const Header23 = (props: Header23Props) => {
|
||||
const { heading, description, buttons } = {
|
||||
const { heading, description, buttons, children } = {
|
||||
...Header23Defaults,
|
||||
...props,
|
||||
};
|
||||
@ -44,6 +44,7 @@ export const Header23 = (props: Header23Props) => {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -4,6 +4,9 @@ import { Button, useMediaQuery } from "@relume_io/relume-ui";
|
||||
import type { ButtonProps } from "@relume_io/relume-ui";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { RxChevronDown } from "react-icons/rx";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
type NavButtonProps = ButtonProps & { url?: string };
|
||||
|
||||
type ImageProps = {
|
||||
url?: string;
|
||||
@ -20,7 +23,7 @@ type NavLink = {
|
||||
type Props = {
|
||||
logo: ImageProps;
|
||||
navLinks: NavLink[];
|
||||
buttons: ButtonProps[];
|
||||
buttons: NavButtonProps[];
|
||||
};
|
||||
|
||||
export type Navbar3Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;
|
||||
@ -89,11 +92,15 @@ export const Navbar3 = (props: Navbar3Props) => {
|
||||
</div>
|
||||
))}
|
||||
<div className="mt-6 lg:hidden">
|
||||
{buttons.map((button, index) => (
|
||||
<Button key={index} {...button} className="w-full">
|
||||
{button.title}
|
||||
</Button>
|
||||
))}
|
||||
{buttons.map(({ url, ...button }, index) =>
|
||||
url ? (
|
||||
<Link key={index} to={url}>
|
||||
<Button {...button} className="w-full">{button.title}</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<Button key={index} {...button} className="w-full">{button.title}</Button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
{isMobileMenuOpen && (
|
||||
@ -112,11 +119,19 @@ export const Navbar3 = (props: Navbar3Props) => {
|
||||
</a>
|
||||
<div className="flex min-h-16 items-center justify-end gap-x-4">
|
||||
<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.title}
|
||||
</Button>
|
||||
))}
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -201,6 +216,7 @@ export const Navbar3Defaults: Props = {
|
||||
title: "Tickets",
|
||||
size: "sm",
|
||||
variant: "primary",
|
||||
url: "/tickets",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
79
relume-test/src/components/Pricing1Ticket.tsx
Normal file
79
relume-test/src/components/Pricing1Ticket.tsx
Normal 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;
|
||||
88
relume-test/src/components/Pricing39Ticket.tsx
Normal file
88
relume-test/src/components/Pricing39Ticket.tsx
Normal 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;
|
||||
@ -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'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;
|
||||
Loading…
x
Reference in New Issue
Block a user