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 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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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) =>
|
||||||
<Button key={index} {...button} className="px-5 py-2 bg-electric-violet text-cloud-white rounded-2xl border-electric-violet font-barlow font-semibold">
|
url ? (
|
||||||
{button.title}
|
<Link key={index} to={url}>
|
||||||
</Button>
|
<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>
|
||||||
</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",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
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