Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
images: {
remotePatterns: [
{
hostname: "images.unsplash.com",
},
],
},
};

export default nextConfig;
424 changes: 298 additions & 126 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rn-checkout",
"version": "0.2.0",
"version": "0.3.0",
"private": true,
"scripts": {
"dev": "next dev",
Expand All @@ -17,11 +17,15 @@
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.2",
"@requestnetwork/payment-widget": "^0.3.1",
"@requestnetwork/payment-widget": "^0.3.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^4.1.0",
"embla-carousel-autoplay": "^8.3.1",
"embla-carousel-react": "^8.3.1",
"framer-motion": "^11.3.28",
"lucide-react": "^0.428.0",
"next": "14.2.5",
Expand All @@ -31,7 +35,8 @@
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"validator": "^13.12.0",
"zod": "^3.23.8"
"zod": "^3.23.8",
"zustand": "^5.0.1"
},
"devDependencies": {
"@types/node": "^20",
Expand Down
10 changes: 10 additions & 0 deletions src/app/(demo)/checkout/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { CheckoutStepper } from "@/components/CheckoutStepper";

export default function CheckoutPage() {
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">Checkout</h1>
<CheckoutStepper />
</div>
);
}
112 changes: 112 additions & 0 deletions src/app/(demo)/event/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { notFound } from "next/navigation";
import Image from "next/image";
import { CalendarIcon, MapPinIcon, Clock } from "lucide-react";
import { format } from "date-fns";
import { TicketSelector } from "@/components/TicketSelector";
import eventsData from "@/const/data.json";

async function getEventById(id: string) {
// In a real app, this would be a DB or API call
const event = eventsData.events.find((event) => event.id === id);
if (!event) return null;
return event;
}

export default async function EventDetailsPage({
params,
}: {
params: { id: string };
}) {
const event = await getEventById(params.id);

if (!event) {
notFound();
}

return (
<div className="min-h-screen bg-gray-50">
{/* Hero Section */}
<div className="relative h-[400px] w-full">
<Image
src={event.headerImage}
alt={event.name}
fill
className="object-cover"
priority
/>
<div className="absolute inset-0 bg-black/50" />
<div className="absolute bottom-8 left-8">
<span className="inline-block px-3 py-1 mb-4 text-sm font-medium text-white bg-[#099C77] rounded-full">
{event.type}
</span>
<h1 className="text-4xl font-bold text-white">{event.name}</h1>
</div>
</div>

{/* Content Section */}
<div className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Main Content */}
<div className="lg:col-span-2 space-y-8">
{/* Event Details */}
<section className="bg-white rounded-xl p-6 shadow-sm">
<h2 className="text-2xl font-semibold mb-4">Event Details</h2>
<div className="space-y-4">
<div className="flex items-center gap-3">
<CalendarIcon className="w-5 h-5 text-[#099C77]" />
<span>
{format(new Date(event.dateTime), "MMMM d, yyyy")}
</span>
</div>
<div className="flex items-center gap-3">
<Clock className="w-5 h-5 text-[#099C77]" />
<span>
{format(new Date(event.dateTime), "h:mm a")} -{" "}
{format(new Date(event.endDateTime), "h:mm a")}
</span>
</div>
<div className="flex items-center gap-3">
<MapPinIcon className="w-5 h-5 text-[#099C77]" />
<span>
{event.location.venue}, {event.location.city},{" "}
{event.location.country}
</span>
</div>
</div>
</section>

{/* About Section */}
<section className="bg-white rounded-xl p-6 shadow-sm">
<h2 className="text-2xl font-semibold mb-4">About the Event</h2>
<p className="text-gray-600">{event.organizer.description}</p>
</section>

{/* Organizer Section */}
<section className="bg-white rounded-xl p-6 shadow-sm">
<h2 className="text-2xl font-semibold mb-4">Organizer</h2>
<div className="flex items-center gap-4">
<div className="relative h-16 w-16 rounded-full overflow-hidden">
<Image
src={event.organizer.logo}
alt={event.organizer.name}
fill
className="object-cover"
/>
</div>
<div>
<h3 className="font-semibold">{event.organizer.name}</h3>
<p className="text-sm text-gray-600">Event Organizer</p>
</div>
</div>
</section>
</div>

{/* Ticket Selection Sidebar */}
<div className="lg:col-span-1">
<TicketSelector event={event} />
</div>
</div>
</div>
</div>
);
}
66 changes: 66 additions & 0 deletions src/app/(demo)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { EventShowcase } from "@/components/EventShowcase";
import {
Carousel,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import eventData from "@/const/data.json";
import Image from "next/image";
import Link from "next/link";

export const metadata = {
title: "Request Checkout Demo",
description:
"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",
};

export default function DemoPage() {
const events = eventData.events;
const featuredEvents = events.filter((event) => event.featured);

return (
<>
<section className="flex flex-col gap-2">
{/* Featured Events Carousel */}
<div className="mb-8">
<Carousel
className="w-full"
autoplay
aria-label="Featured Events Slideshow"
>
<CarouselContent>
{featuredEvents.map((event) => (
<CarouselItem key={event.id}>
<Link
href={`/events/${event.id}`}
aria-label={`View details for ${event.name}`}
>
<div className="relative aspect-[3/1] w-full overflow-hidden rounded-lg">
<Image
src={event.headerImage}
alt={event.name}
fill
className="object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="absolute bottom-4 left-4 right-4 text-white">
<div className="mb-2">
<span className="inline-block px-2 py-1 text-xs font-semibold bg-white/20 rounded-full">
{event.type}
</span>
</div>
<h2 className="text-2xl font-bold">{event.name}</h2>
<p className="text-sm text-white/80">Featured Event</p>
</div>
</div>
</Link>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
</div>
<EventShowcase events={events} />
</section>
</>
);
}
4 changes: 3 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export default function RootLayout({
<html lang="en">
<body className={`${montserrat.className} pb-24`}>
<Navbar />
{children}
<main className="max-w-[1200px] w-full mx-auto px-5 py-8">
{children}
</main>
<TooltipProvider>
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 z-50">
<Dock direction="middle">
Expand Down
14 changes: 10 additions & 4 deletions src/app/page.tsx → src/app/playground/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Playground } from "@/components/Playground";

export default function Home() {
export const metadata = {
title: "Request Checkout Playground",
description:
"A playground for the Request Checkout widget. You can experiment with the widget's properties, such as seller information, product details, and supported currencies.",
};

export default function PlaygroundPage() {
return (
<main className="flex flex-col gap-4 max-w-[1200px] w-full mx-auto px-5 py-8">
<h1 className="font-bold text-4xl mb-4">Request Checkout</h1>
<>
<h1 className="font-bold text-4xl mb-4">Request Checkout Playground</h1>

<div className="flex flex-col gap-2">
<p>
Expand Down Expand Up @@ -46,6 +52,6 @@ export default function Home() {
<Playground />
</section>
</div>
</main>
</>
);
}
107 changes: 107 additions & 0 deletions src/components/CartReview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"use client";

import { useTicketStore } from "@/store/ticketStore";
import { useEffect, useState } from "react";

export function CartReview() {
const { tickets, incrementQuantity, decrementQuantity, clearTickets } =
useTicketStore();
const [total, setTotal] = useState(0);

useEffect(() => {
const newTotal = Object.values(tickets).reduce(
(sum, ticket) => sum + ticket.price * ticket.quantity,
0
);
setTotal(newTotal);
}, [tickets]);

const groupedTickets = Object.entries(tickets).map(([key, ticket]) => ({
eventId: key.split("-")[0],
...ticket,
}));

return (
<div
className="bg-white rounded-xl p-6 shadow-sm"
role="region"
aria-label="Shopping Cart"
>
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-semibold">Cart Review</h2>
{groupedTickets.length > 0 && (
<button
onClick={clearTickets}
className="text-red-500 hover:text-red-600 text-sm font-medium"
aria-label="Clear all items from cart"
>
Clear Cart
</button>
)}
</div>

{groupedTickets.length === 0 ? (
<p className="text-gray-500">Your cart is empty</p>
) : (
<div>
<div className="max-h-[300px] overflow-y-auto space-y-4 pr-2">
{groupedTickets.map((ticket) => (
<div
key={ticket.id}
className="border border-gray-100 rounded-lg p-4 bg-gray-50"
>
<div className="flex justify-between items-start gap-4">
<div>
<h3 className="font-medium text-gray-900">{ticket.name}</h3>
<p className="text-[#099C77] font-medium">
${ticket.price.toFixed(2)}
</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => decrementQuantity(ticket.id)}
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"
aria-label={`Decrease quantity for ${ticket.name}`}
>
-
</button>
<span
className="w-8 text-center font-medium"
aria-label={`${ticket.quantity} tickets selected`}
>
{ticket.quantity}
</span>
<button
onClick={() =>
incrementQuantity(ticket.id, {
id: ticket.id,
name: ticket.name,
price: ticket.price,
description: "",
available: 0,
})
}
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"
aria-label={`Increase quantity for ${ticket.name}`}
>
+
</button>
</div>
</div>
</div>
))}
</div>

<div className="mt-6 pt-6 border-t border-gray-200">
<div className="flex justify-between items-center">
<span className="font-medium text-gray-900">Total:</span>
<span className="text-xl font-bold text-[#099C77]">
${total > 0 ? total.toFixed(2) : "0.00"}
</span>
</div>
</div>
</div>
)}
</div>
);
}
Loading