Skip to content

Commit eae796c

Browse files
authored
refactor: move playground section to its own page to add the demo (#22)
1 parent 7b060d6 commit eae796c

21 files changed

+2058
-239
lines changed

next.config.mjs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
/** @type {import('next').NextConfig} */
2-
const nextConfig = {};
2+
const nextConfig = {
3+
images: {
4+
remotePatterns: [
5+
{
6+
hostname: "images.unsplash.com",
7+
},
8+
],
9+
},
10+
};
311

412
export default nextConfig;

package-lock.json

Lines changed: 298 additions & 126 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rn-checkout",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",
@@ -17,11 +17,15 @@
1717
"@radix-ui/react-popover": "^1.1.1",
1818
"@radix-ui/react-slot": "^1.1.0",
1919
"@radix-ui/react-switch": "^1.1.0",
20+
"@radix-ui/react-tabs": "^1.1.1",
2021
"@radix-ui/react-tooltip": "^1.1.2",
21-
"@requestnetwork/payment-widget": "^0.3.1",
22+
"@requestnetwork/payment-widget": "^0.3.2",
2223
"class-variance-authority": "^0.7.0",
2324
"clsx": "^2.1.1",
2425
"cmdk": "^1.0.0",
26+
"date-fns": "^4.1.0",
27+
"embla-carousel-autoplay": "^8.3.1",
28+
"embla-carousel-react": "^8.3.1",
2529
"framer-motion": "^11.3.28",
2630
"lucide-react": "^0.428.0",
2731
"next": "14.2.5",
@@ -31,7 +35,8 @@
3135
"tailwind-merge": "^2.5.2",
3236
"tailwindcss-animate": "^1.0.7",
3337
"validator": "^13.12.0",
34-
"zod": "^3.23.8"
38+
"zod": "^3.23.8",
39+
"zustand": "^5.0.1"
3540
},
3641
"devDependencies": {
3742
"@types/node": "^20",

src/app/(demo)/checkout/page.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { CheckoutStepper } from "@/components/CheckoutStepper";
2+
3+
export default function CheckoutPage() {
4+
return (
5+
<div className="container mx-auto px-4 py-8">
6+
<h1 className="text-3xl font-bold mb-8">Checkout</h1>
7+
<CheckoutStepper />
8+
</div>
9+
);
10+
}

src/app/(demo)/event/[id]/page.tsx

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { notFound } from "next/navigation";
2+
import Image from "next/image";
3+
import { CalendarIcon, MapPinIcon, Clock } from "lucide-react";
4+
import { format } from "date-fns";
5+
import { TicketSelector } from "@/components/TicketSelector";
6+
import eventsData from "@/const/data.json";
7+
8+
async function getEventById(id: string) {
9+
// In a real app, this would be a DB or API call
10+
const event = eventsData.events.find((event) => event.id === id);
11+
if (!event) return null;
12+
return event;
13+
}
14+
15+
export default async function EventDetailsPage({
16+
params,
17+
}: {
18+
params: { id: string };
19+
}) {
20+
const event = await getEventById(params.id);
21+
22+
if (!event) {
23+
notFound();
24+
}
25+
26+
return (
27+
<div className="min-h-screen bg-gray-50">
28+
{/* Hero Section */}
29+
<div className="relative h-[400px] w-full">
30+
<Image
31+
src={event.headerImage}
32+
alt={event.name}
33+
fill
34+
className="object-cover"
35+
priority
36+
/>
37+
<div className="absolute inset-0 bg-black/50" />
38+
<div className="absolute bottom-8 left-8">
39+
<span className="inline-block px-3 py-1 mb-4 text-sm font-medium text-white bg-[#099C77] rounded-full">
40+
{event.type}
41+
</span>
42+
<h1 className="text-4xl font-bold text-white">{event.name}</h1>
43+
</div>
44+
</div>
45+
46+
{/* Content Section */}
47+
<div className="container mx-auto px-4 py-8">
48+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
49+
{/* Main Content */}
50+
<div className="lg:col-span-2 space-y-8">
51+
{/* Event Details */}
52+
<section className="bg-white rounded-xl p-6 shadow-sm">
53+
<h2 className="text-2xl font-semibold mb-4">Event Details</h2>
54+
<div className="space-y-4">
55+
<div className="flex items-center gap-3">
56+
<CalendarIcon className="w-5 h-5 text-[#099C77]" />
57+
<span>
58+
{format(new Date(event.dateTime), "MMMM d, yyyy")}
59+
</span>
60+
</div>
61+
<div className="flex items-center gap-3">
62+
<Clock className="w-5 h-5 text-[#099C77]" />
63+
<span>
64+
{format(new Date(event.dateTime), "h:mm a")} -{" "}
65+
{format(new Date(event.endDateTime), "h:mm a")}
66+
</span>
67+
</div>
68+
<div className="flex items-center gap-3">
69+
<MapPinIcon className="w-5 h-5 text-[#099C77]" />
70+
<span>
71+
{event.location.venue}, {event.location.city},{" "}
72+
{event.location.country}
73+
</span>
74+
</div>
75+
</div>
76+
</section>
77+
78+
{/* About Section */}
79+
<section className="bg-white rounded-xl p-6 shadow-sm">
80+
<h2 className="text-2xl font-semibold mb-4">About the Event</h2>
81+
<p className="text-gray-600">{event.organizer.description}</p>
82+
</section>
83+
84+
{/* Organizer Section */}
85+
<section className="bg-white rounded-xl p-6 shadow-sm">
86+
<h2 className="text-2xl font-semibold mb-4">Organizer</h2>
87+
<div className="flex items-center gap-4">
88+
<div className="relative h-16 w-16 rounded-full overflow-hidden">
89+
<Image
90+
src={event.organizer.logo}
91+
alt={event.organizer.name}
92+
fill
93+
className="object-cover"
94+
/>
95+
</div>
96+
<div>
97+
<h3 className="font-semibold">{event.organizer.name}</h3>
98+
<p className="text-sm text-gray-600">Event Organizer</p>
99+
</div>
100+
</div>
101+
</section>
102+
</div>
103+
104+
{/* Ticket Selection Sidebar */}
105+
<div className="lg:col-span-1">
106+
<TicketSelector event={event} />
107+
</div>
108+
</div>
109+
</div>
110+
</div>
111+
);
112+
}

src/app/(demo)/page.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { EventShowcase } from "@/components/EventShowcase";
2+
import {
3+
Carousel,
4+
CarouselContent,
5+
CarouselItem,
6+
} from "@/components/ui/carousel";
7+
import eventData from "@/const/data.json";
8+
import Image from "next/image";
9+
import Link from "next/link";
10+
11+
export const metadata = {
12+
title: "Request Checkout Demo",
13+
description:
14+
"This is a demo of the Request Checkout widget. It is a pre-built component that abstracts all the complexities of blockchain transactions using Request Network, making it simple for businesses to handle crypto-to-crypto payments without deep technical knowledge",
15+
};
16+
17+
export default function DemoPage() {
18+
const events = eventData.events;
19+
const featuredEvents = events.filter((event) => event.featured);
20+
21+
return (
22+
<>
23+
<section className="flex flex-col gap-2">
24+
{/* Featured Events Carousel */}
25+
<div className="mb-8">
26+
<Carousel
27+
className="w-full"
28+
autoplay
29+
aria-label="Featured Events Slideshow"
30+
>
31+
<CarouselContent>
32+
{featuredEvents.map((event) => (
33+
<CarouselItem key={event.id}>
34+
<Link
35+
href={`/events/${event.id}`}
36+
aria-label={`View details for ${event.name}`}
37+
>
38+
<div className="relative aspect-[3/1] w-full overflow-hidden rounded-lg">
39+
<Image
40+
src={event.headerImage}
41+
alt={event.name}
42+
fill
43+
className="object-cover"
44+
/>
45+
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
46+
<div className="absolute bottom-4 left-4 right-4 text-white">
47+
<div className="mb-2">
48+
<span className="inline-block px-2 py-1 text-xs font-semibold bg-white/20 rounded-full">
49+
{event.type}
50+
</span>
51+
</div>
52+
<h2 className="text-2xl font-bold">{event.name}</h2>
53+
<p className="text-sm text-white/80">Featured Event</p>
54+
</div>
55+
</div>
56+
</Link>
57+
</CarouselItem>
58+
))}
59+
</CarouselContent>
60+
</Carousel>
61+
</div>
62+
<EventShowcase events={events} />
63+
</section>
64+
</>
65+
);
66+
}

src/app/layout.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ export default function RootLayout({
4949
<html lang="en">
5050
<body className={`${montserrat.className} pb-24`}>
5151
<Navbar />
52-
{children}
52+
<main className="max-w-[1200px] w-full mx-auto px-5 py-8">
53+
{children}
54+
</main>
5355
<TooltipProvider>
5456
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 z-50">
5557
<Dock direction="middle">

src/app/page.tsx renamed to src/app/playground/page.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import { Playground } from "@/components/Playground";
22

3-
export default function Home() {
3+
export const metadata = {
4+
title: "Request Checkout Playground",
5+
description:
6+
"A playground for the Request Checkout widget. You can experiment with the widget's properties, such as seller information, product details, and supported currencies.",
7+
};
8+
9+
export default function PlaygroundPage() {
410
return (
5-
<main className="flex flex-col gap-4 max-w-[1200px] w-full mx-auto px-5 py-8">
6-
<h1 className="font-bold text-4xl mb-4">Request Checkout</h1>
11+
<>
12+
<h1 className="font-bold text-4xl mb-4">Request Checkout Playground</h1>
713

814
<div className="flex flex-col gap-2">
915
<p>
@@ -46,6 +52,6 @@ export default function Home() {
4652
<Playground />
4753
</section>
4854
</div>
49-
</main>
55+
</>
5056
);
5157
}

src/components/CartReview.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"use client";
2+
3+
import { useTicketStore } from "@/store/ticketStore";
4+
import { useEffect, useState } from "react";
5+
6+
export function CartReview() {
7+
const { tickets, incrementQuantity, decrementQuantity, clearTickets } =
8+
useTicketStore();
9+
const [total, setTotal] = useState(0);
10+
11+
useEffect(() => {
12+
const newTotal = Object.values(tickets).reduce(
13+
(sum, ticket) => sum + ticket.price * ticket.quantity,
14+
0
15+
);
16+
setTotal(newTotal);
17+
}, [tickets]);
18+
19+
const groupedTickets = Object.entries(tickets).map(([key, ticket]) => ({
20+
eventId: key.split("-")[0],
21+
...ticket,
22+
}));
23+
24+
return (
25+
<div
26+
className="bg-white rounded-xl p-6 shadow-sm"
27+
role="region"
28+
aria-label="Shopping Cart"
29+
>
30+
<div className="flex justify-between items-center mb-6">
31+
<h2 className="text-2xl font-semibold">Cart Review</h2>
32+
{groupedTickets.length > 0 && (
33+
<button
34+
onClick={clearTickets}
35+
className="text-red-500 hover:text-red-600 text-sm font-medium"
36+
aria-label="Clear all items from cart"
37+
>
38+
Clear Cart
39+
</button>
40+
)}
41+
</div>
42+
43+
{groupedTickets.length === 0 ? (
44+
<p className="text-gray-500">Your cart is empty</p>
45+
) : (
46+
<div>
47+
<div className="max-h-[300px] overflow-y-auto space-y-4 pr-2">
48+
{groupedTickets.map((ticket) => (
49+
<div
50+
key={ticket.id}
51+
className="border border-gray-100 rounded-lg p-4 bg-gray-50"
52+
>
53+
<div className="flex justify-between items-start gap-4">
54+
<div>
55+
<h3 className="font-medium text-gray-900">{ticket.name}</h3>
56+
<p className="text-[#099C77] font-medium">
57+
${ticket.price.toFixed(2)}
58+
</p>
59+
</div>
60+
<div className="flex items-center gap-2">
61+
<button
62+
onClick={() => decrementQuantity(ticket.id)}
63+
className="w-7 h-7 rounded-lg border border-gray-200 flex items-center justify-center text-gray-600 hover:border-[#099C77] hover:text-[#099C77] transition-colors"
64+
aria-label={`Decrease quantity for ${ticket.name}`}
65+
>
66+
-
67+
</button>
68+
<span
69+
className="w-8 text-center font-medium"
70+
aria-label={`${ticket.quantity} tickets selected`}
71+
>
72+
{ticket.quantity}
73+
</span>
74+
<button
75+
onClick={() =>
76+
incrementQuantity(ticket.id, {
77+
id: ticket.id,
78+
name: ticket.name,
79+
price: ticket.price,
80+
description: "",
81+
available: 0,
82+
})
83+
}
84+
className="w-7 h-7 rounded-lg border border-gray-200 flex items-center justify-center text-gray-600 hover:border-[#099C77] hover:text-[#099C77] transition-colors"
85+
aria-label={`Increase quantity for ${ticket.name}`}
86+
>
87+
+
88+
</button>
89+
</div>
90+
</div>
91+
</div>
92+
))}
93+
</div>
94+
95+
<div className="mt-6 pt-6 border-t border-gray-200">
96+
<div className="flex justify-between items-center">
97+
<span className="font-medium text-gray-900">Total:</span>
98+
<span className="text-xl font-bold text-[#099C77]">
99+
${total > 0 ? total.toFixed(2) : "0.00"}
100+
</span>
101+
</div>
102+
</div>
103+
</div>
104+
)}
105+
</div>
106+
);
107+
}

0 commit comments

Comments
 (0)