= 2 ? "text-green-600" : "text-gray-400"}`}>
-
= 2 ? "bg-green-600" : "bg-gray-400"}`}>
+
= 2 ? "text-green-600" : "text-gray-400"}`}
+ >
+
= 2 ? "bg-green-600" : "bg-gray-400"}`}
+ >
Buy Tickets (Event Manager → Ticket NFT)
-
= 3 ? "text-green-600" : "text-gray-400"}`}>
-
= 3 ? "bg-green-600" : "bg-gray-400"}`}>
+
= 3 ? "text-green-600" : "text-gray-400"}`}
+ >
+
= 3 ? "bg-green-600" : "bg-gray-400"}`}
+ >
List on Marketplace (optional)
@@ -296,11 +355,15 @@ export default function OrchestrationExamplePage() {
Logs
{logs.length === 0 ? (
-
No logs yet. Run the orchestration to see the process.
+
+ No logs yet. Run the orchestration to see the process.
+
) : (
{logs.map((log, index) => (
-
{log}
+
+ {log}
+
))}
)}
@@ -310,4 +373,4 @@ export default function OrchestrationExamplePage() {
);
-}
\ No newline at end of file
+}
diff --git a/soroban-client/app/analytics/page.tsx b/soroban-client/app/analytics/page.tsx
index 7c2dc019..156e508d 100644
--- a/soroban-client/app/analytics/page.tsx
+++ b/soroban-client/app/analytics/page.tsx
@@ -18,8 +18,9 @@ export default function AnalyticsPage() {
Organizer and platform insights in one dashboard
- Track page views, wallet connections, batch ticket purchases, conversion
- trends, and revenue signals without leaving the CrowdPass interface.
+ Track page views, wallet connections, batch ticket purchases,
+ conversion trends, and revenue signals without leaving the CrowdPass
+ interface.
diff --git a/soroban-client/app/api/events/route.ts b/soroban-client/app/api/events/route.ts
index 098515f3..d68938d9 100644
--- a/soroban-client/app/api/events/route.ts
+++ b/soroban-client/app/api/events/route.ts
@@ -14,7 +14,11 @@
*/
import { NextRequest, NextResponse } from "next/server";
-import { queryEvents, getCacheStats, type ContractEventType } from "@/lib/indexer";
+import {
+ queryEvents,
+ getCacheStats,
+ type ContractEventType,
+} from "@/lib/indexer";
export const dynamic = "force-dynamic";
@@ -24,7 +28,8 @@ export async function GET(req: NextRequest) {
try {
const result = await queryEvents({
organizer: sp.get("organizer") ?? undefined,
- status: (sp.get("status") as "active" | "canceled" | "completed") ?? undefined,
+ status:
+ (sp.get("status") as "active" | "canceled" | "completed") ?? undefined,
type: (sp.get("type") as ContractEventType) ?? undefined,
from: sp.get("from") ? Number(sp.get("from")) : undefined,
to: sp.get("to") ? Number(sp.get("to")) : undefined,
diff --git a/soroban-client/app/api/events/stream/route.ts b/soroban-client/app/api/events/stream/route.ts
index 48759fa5..e15fa6ab 100644
--- a/soroban-client/app/api/events/stream/route.ts
+++ b/soroban-client/app/api/events/stream/route.ts
@@ -17,7 +17,11 @@
*/
import { NextRequest } from "next/server";
-import { getIndexedEvents, getEventsAfterCursor, getLatestCursor } from "@/lib/indexer";
+import {
+ getIndexedEvents,
+ getEventsAfterCursor,
+ getLatestCursor,
+} from "@/lib/indexer";
export const dynamic = "force-dynamic";
@@ -30,7 +34,7 @@ export async function GET(req: NextRequest) {
let cursor = "";
// Extract cursor from query params for reconnection
- const urlCursor = req.nextUrl.searchParams.get('cursor');
+ const urlCursor = req.nextUrl.searchParams.get("cursor");
if (urlCursor) {
cursor = urlCursor;
}
@@ -54,18 +58,25 @@ export async function GET(req: NextRequest) {
if (cursor) {
// Replay events after the cursor for reconnection
initialEvents = getEventsAfterCursor(cursor);
- send("reconnect", JSON.stringify({
- events: initialEvents,
- type: "replay",
- cursor: cursor
- }));
+ send(
+ "reconnect",
+ JSON.stringify({
+ events: initialEvents,
+ type: "replay",
+ cursor: cursor,
+ }),
+ );
} else {
// Initial snapshot
initialEvents = await getIndexedEvents();
if (initialEvents.length > 0) {
lastEventId = initialEvents[initialEvents.length - 1].id;
}
- send("events", JSON.stringify({ events: initialEvents, type: "snapshot" }), lastEventId || undefined);
+ send(
+ "events",
+ JSON.stringify({ events: initialEvents, type: "snapshot" }),
+ lastEventId || undefined,
+ );
}
// Update cursor tracking
@@ -85,7 +96,11 @@ export async function GET(req: NextRequest) {
if (newEvents.length > 0) {
lastEventId = newEvents[newEvents.length - 1].id;
cursor = `${newEvents[newEvents.length - 1].ledger}-0-0-0`;
- send("events", JSON.stringify({ events: newEvents, type: "update" }), lastEventId);
+ send(
+ "events",
+ JSON.stringify({ events: newEvents, type: "update" }),
+ lastEventId,
+ );
}
} catch {
// swallow — client will reconnect via SSE retry
@@ -102,7 +117,11 @@ export async function GET(req: NextRequest) {
closed = true;
clearInterval(pollTimer);
clearInterval(heartbeatTimer);
- try { controller.close(); } catch { /* already closed */ }
+ try {
+ controller.close();
+ } catch {
+ /* already closed */
+ }
};
// ReadableStream cancel is called on client disconnect
diff --git a/soroban-client/app/api/marketplace/listings/route.ts b/soroban-client/app/api/marketplace/listings/route.ts
index d5c9242e..4544e7ab 100644
--- a/soroban-client/app/api/marketplace/listings/route.ts
+++ b/soroban-client/app/api/marketplace/listings/route.ts
@@ -5,7 +5,7 @@ export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const seller = searchParams.get("seller");
-
+
// TODO: Replace with actual contract calls
const mockListings = [
{
@@ -21,16 +21,16 @@ export async function GET(request: NextRequest) {
},
// Add more mock listings
];
-
- const filtered = seller
- ? mockListings.filter(l => l.seller === seller)
+
+ const filtered = seller
+ ? mockListings.filter((l) => l.seller === seller)
: mockListings;
-
+
return NextResponse.json(filtered);
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch listings" },
- { status: 500 }
+ { status: 500 },
);
}
}
diff --git a/soroban-client/app/api/marketplace/purchase/route.ts b/soroban-client/app/api/marketplace/purchase/route.ts
index 240777b6..ba860b75 100644
--- a/soroban-client/app/api/marketplace/purchase/route.ts
+++ b/soroban-client/app/api/marketplace/purchase/route.ts
@@ -4,19 +4,16 @@ import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
try {
const { listingId, buyerAddress, price } = await request.json();
-
+
// TODO: Call marketplace contract purchase_ticket function
// This would:
// 1. Verify buyer has sufficient funds
// 2. Transfer XLM from buyer to seller
// 3. Transfer NFT from seller to buyer
// 4. Mark listing as inactive
-
+
return NextResponse.json({ success: true });
} catch (error) {
- return NextResponse.json(
- { error: "Purchase failed" },
- { status: 400 }
- );
+ return NextResponse.json({ error: "Purchase failed" }, { status: 400 });
}
-}
\ No newline at end of file
+}
diff --git a/soroban-client/app/create-event/page.tsx b/soroban-client/app/create-event/page.tsx
index 87035042..3ce24180 100644
--- a/soroban-client/app/create-event/page.tsx
+++ b/soroban-client/app/create-event/page.tsx
@@ -81,7 +81,7 @@ export default function CreateEventPage() {
organizerAddress = localStorage.getItem("wallet_address");
} else {
alert(
- `Please install ${providerName} (or another Stellar wallet) to create an event.`
+ `Please install ${providerName} (or another Stellar wallet) to create an event.`,
);
return;
}
@@ -99,7 +99,7 @@ export default function CreateEventPage() {
const startUnix = Math.floor(new Date(data.startDate).getTime() / 1000);
const endUnix = Math.floor(new Date(data.endDate).getTime() / 1000);
const ticketPrice = BigInt(
- Math.floor(parseFloat(data.price) * 10_000_000)
+ Math.floor(parseFloat(data.price) * 10_000_000),
);
const totalTickets = BigInt(parseInt(data.tickets, 10));
@@ -117,7 +117,7 @@ export default function CreateEventPage() {
totalTickets,
paymentToken,
},
- signTransaction
+ signTransaction,
);
setSuccessMsg(`Event created (ledger ${res.ledger}, tx ${res.hash})`);
diff --git a/soroban-client/app/dashboard/page.tsx b/soroban-client/app/dashboard/page.tsx
index daefa3e9..ff3639cf 100644
--- a/soroban-client/app/dashboard/page.tsx
+++ b/soroban-client/app/dashboard/page.tsx
@@ -107,10 +107,10 @@ function UpdateEventModal({
const { address, signTransaction } = useWallet();
const [theme, setTheme] = useState(event.theme);
const [price, setPrice] = useState(
- (Number(event.ticket_price) / 1e7).toString()
+ (Number(event.ticket_price) / 1e7).toString(),
);
const [totalTickets, setTotalTickets] = useState(
- event.total_tickets.toString()
+ event.total_tickets.toString(),
);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState("");
@@ -135,7 +135,7 @@ function UpdateEventModal({
? BigInt(totalTickets)
: undefined,
},
- signTransaction
+ signTransaction,
);
onSuccess();
onClose();
@@ -309,7 +309,7 @@ export default function DashboardPage() {
const [actionMsg, setActionMsg] = useState("");
const myEvents = events.filter(
- (e) => e.organizer.toLowerCase() === address?.toLowerCase()
+ (e) => e.organizer.toLowerCase() === address?.toLowerCase(),
);
const fetchEvents = useCallback(async () => {
@@ -359,7 +359,9 @@ export default function DashboardPage() {
Organizer Dashboard
-
Connect your wallet to manage your events.
+
+ Connect your wallet to manage your events.
+
{
if (!isInstalled) {
@@ -431,8 +433,8 @@ export default function DashboardPage() {
value: formatXLM(
myEvents.reduce(
(s, e) => s + e.tickets_sold * e.ticket_price,
- BigInt(0)
- )
+ BigInt(0),
+ ),
),
},
].map(({ label, value }) => (
@@ -473,7 +475,9 @@ export default function DashboardPage() {
{error}
) : myEvents.length === 0 ? (
-
You haven't created any events yet.
+
+ You haven't created any events yet.
+
- Browse
+
+ Browse
+
Discover events and buy tickets on-chain
- Search and filter live listings from the EventManager contract, review dates
- and inventory, then confirm purchases with your wallet (Freighter by default).
- The same experience is available at{" "}
+ Search and filter live listings from the EventManager contract,
+ review dates and inventory, then confirm purchases with your wallet
+ (Freighter by default). The same experience is available at{" "}
/browse .
diff --git a/soroban-client/app/examples/soroban-integration/page.tsx b/soroban-client/app/examples/soroban-integration/page.tsx
index 09cbc559..2e520c52 100644
--- a/soroban-client/app/examples/soroban-integration/page.tsx
+++ b/soroban-client/app/examples/soroban-integration/page.tsx
@@ -1,27 +1,35 @@
"use client";
-import { SorobanProvider } from '@/contexts/SorobanContext';
-import { EventList } from '@/components/examples/EventList';
-import { CreateEventForm } from '@/components/examples/CreateEventForm';
-import { PurchaseTicket } from '@/components/examples/PurchaseTicket';
-import { TBAManager } from '@/components/examples/TBAManager';
-import { useState } from 'react';
+import { SorobanProvider } from "@/contexts/SorobanContext";
+import { EventList } from "@/components/examples/EventList";
+import { CreateEventForm } from "@/components/examples/CreateEventForm";
+import { PurchaseTicket } from "@/components/examples/PurchaseTicket";
+import { TBAManager } from "@/components/examples/TBAManager";
+import { useState } from "react";
const sorobanConfig = {
- horizonUrl: process.env.NEXT_PUBLIC_HORIZON_URL || 'https://horizon-testnet.stellar.org',
- sorobanRpcUrl: process.env.NEXT_PUBLIC_SOROBAN_RPC_URL || 'https://soroban-testnet.stellar.org',
- networkPassphrase: process.env.NEXT_PUBLIC_NETWORK_PASSPHRASE || 'Test SDF Network ; September 2015',
+ horizonUrl:
+ process.env.NEXT_PUBLIC_HORIZON_URL ||
+ "https://horizon-testnet.stellar.org",
+ sorobanRpcUrl:
+ process.env.NEXT_PUBLIC_SOROBAN_RPC_URL ||
+ "https://soroban-testnet.stellar.org",
+ networkPassphrase:
+ process.env.NEXT_PUBLIC_NETWORK_PASSPHRASE ||
+ "Test SDF Network ; September 2015",
contracts: {
- eventManager: process.env.NEXT_PUBLIC_EVENT_MANAGER_CONTRACT || '',
- ticketFactory: process.env.NEXT_PUBLIC_TICKET_FACTORY_CONTRACT || '',
- ticketNft: process.env.NEXT_PUBLIC_TICKET_NFT_CONTRACT || '',
- tbaRegistry: process.env.NEXT_PUBLIC_TBA_REGISTRY_CONTRACT || '',
- tbaAccount: process.env.NEXT_PUBLIC_TBA_ACCOUNT_CONTRACT || '',
+ eventManager: process.env.NEXT_PUBLIC_EVENT_MANAGER_CONTRACT || "",
+ ticketFactory: process.env.NEXT_PUBLIC_TICKET_FACTORY_CONTRACT || "",
+ ticketNft: process.env.NEXT_PUBLIC_TICKET_NFT_CONTRACT || "",
+ tbaRegistry: process.env.NEXT_PUBLIC_TBA_REGISTRY_CONTRACT || "",
+ tbaAccount: process.env.NEXT_PUBLIC_TBA_ACCOUNT_CONTRACT || "",
},
};
export default function SorobanIntegrationPage() {
- const [activeTab, setActiveTab] = useState<'events' | 'create' | 'purchase' | 'tba'>('events');
+ const [activeTab, setActiveTab] = useState<
+ "events" | "create" | "purchase" | "tba"
+ >("events");
const [selectedEventId, setSelectedEventId] = useState(1);
return (
@@ -29,7 +37,9 @@ export default function SorobanIntegrationPage() {
- Soroban Contract Integration
+
+ Soroban Contract Integration
+
Practical demonstration of Soroban contract interactions in React
@@ -38,41 +48,41 @@ export default function SorobanIntegrationPage() {
setActiveTab('events')}
+ onClick={() => setActiveTab("events")}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'events'
- ? 'border-blue-600 text-blue-600'
- : 'border-transparent text-gray-600 hover:text-gray-900'
+ activeTab === "events"
+ ? "border-blue-600 text-blue-600"
+ : "border-transparent text-gray-600 hover:text-gray-900"
}`}
>
Event List
setActiveTab('create')}
+ onClick={() => setActiveTab("create")}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'create'
- ? 'border-blue-600 text-blue-600'
- : 'border-transparent text-gray-600 hover:text-gray-900'
+ activeTab === "create"
+ ? "border-blue-600 text-blue-600"
+ : "border-transparent text-gray-600 hover:text-gray-900"
}`}
>
Create Event
setActiveTab('purchase')}
+ onClick={() => setActiveTab("purchase")}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'purchase'
- ? 'border-blue-600 text-blue-600'
- : 'border-transparent text-gray-600 hover:text-gray-900'
+ activeTab === "purchase"
+ ? "border-blue-600 text-blue-600"
+ : "border-transparent text-gray-600 hover:text-gray-900"
}`}
>
Purchase Ticket
setActiveTab('tba')}
+ onClick={() => setActiveTab("tba")}
className={`px-4 py-2 font-medium border-b-2 transition-colors ${
- activeTab === 'tba'
- ? 'border-blue-600 text-blue-600'
- : 'border-transparent text-gray-600 hover:text-gray-900'
+ activeTab === "tba"
+ ? "border-blue-600 text-blue-600"
+ : "border-transparent text-gray-600 hover:text-gray-900"
}`}
>
TBA Manager
@@ -81,12 +91,14 @@ export default function SorobanIntegrationPage() {
- {activeTab === 'events' && }
- {activeTab === 'create' && }
- {activeTab === 'purchase' && (
+ {activeTab === "events" && }
+ {activeTab === "create" && }
+ {activeTab === "purchase" && (
- Event ID
+
+ Event ID
+
)}
- {activeTab === 'tba' && (
+ {activeTab === "tba" && (
- This page demonstrates React hooks for Soroban contract interactions
- with state management and real-time UI updates.
+ This page demonstrates React hooks for Soroban contract
+ interactions with state management and real-time UI updates.
diff --git a/soroban-client/app/globals.css b/soroban-client/app/globals.css
index 0dfdf20f..82405b9d 100644
--- a/soroban-client/app/globals.css
+++ b/soroban-client/app/globals.css
@@ -1,7 +1,7 @@
@import "tailwindcss";
:root {
- --background: #18181B;
+ --background: #18181b;
/* Dark Zinc */
--foreground: #ffffff;
}
@@ -33,4 +33,4 @@ body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
-}
\ No newline at end of file
+}
diff --git a/soroban-client/app/marketplace/page.tsx b/soroban-client/app/marketplace/page.tsx
index 35713e05..bcaaf564 100644
--- a/soroban-client/app/marketplace/page.tsx
+++ b/soroban-client/app/marketplace/page.tsx
@@ -10,13 +10,16 @@ export default function MarketplacePage() {
- Marketplace
+
+ Marketplace
+
Buy and sell tickets peer-to-peer
- List your unused tickets for resale or discover great deals from other fans.
- All transfers are secured by smart contracts with built-in price caps to prevent scalping.
+ List your unused tickets for resale or discover great deals from
+ other fans. All transfers are secured by smart contracts with
+ built-in price caps to prevent scalping.
@@ -24,4 +27,4 @@ export default function MarketplacePage() {
);
-}
\ No newline at end of file
+}
diff --git a/soroban-client/app/my-tickets/page.tsx b/soroban-client/app/my-tickets/page.tsx
index 0e336847..74b4bb35 100644
--- a/soroban-client/app/my-tickets/page.tsx
+++ b/soroban-client/app/my-tickets/page.tsx
@@ -40,17 +40,36 @@ export default function MyTicketsPage() {
-
-
Your Ticket Wallet is Private
-
Connect your wallet to see all your event tickets and access your entry codes.
-
isInstalled ? connect() : alert("Please install Freighter.")}
- className="rounded-2xl bg-sky-500 px-8 py-4 font-bold text-white shadow-lg transition hover:bg-sky-400 active:scale-95"
+
+
- Connect Wallet
-
+
+
+
+
+
+ Your Ticket Wallet is Private
+
+
+ Connect your wallet to see all your event tickets and access your
+ entry codes.
+
+
+ isInstalled ? connect() : alert("Please install Freighter.")
+ }
+ className="rounded-2xl bg-sky-500 px-8 py-4 font-bold text-white shadow-lg transition hover:bg-sky-400 active:scale-95"
+ >
+ Connect Wallet
+
@@ -64,17 +83,26 @@ export default function MyTicketsPage() {
-
-
My Wallet
-
Your Field Access
-
- Manage your on-chain event tickets, view details, and generate verification QR codes for entry.
-
-
-
- Connected As
- {address?.substring(0, 10)}...{address?.slice(-10)}
-
+
+
+ My Wallet
+
+
+ Your Field Access
+
+
+ Manage your on-chain event tickets, view details, and generate
+ verification QR codes for entry.
+
+
+
+
+ Connected As
+
+
+ {address?.substring(0, 10)}...{address?.slice(-10)}
+
+
@@ -84,16 +112,31 @@ export default function MyTicketsPage() {
) : tickets.length === 0 ? (
-
-
Your ticket history is currently empty.
-
+
- Browse Events
-
+
+
+
+
+
+
+ Your ticket history is currently empty.
+
+
+ Browse Events
+
) : (
@@ -103,35 +146,68 @@ export default function MyTicketsPage() {
className="group relative flex flex-col overflow-hidden rounded-[32px] border border-white/10 bg-zinc-900/40 p-1 shadow-md transition-all hover:border-sky-500/40 hover:bg-zinc-900/80"
>
-
-
-
-
-
{ticket.theme}
-
#{ticket.id} • {ticket.event_type}
-
+
+
-
-
-
- {formatDate(ticket.start_date)}
-
-
+
+
+ {ticket.theme}
+
+
+ #{ticket.id} • {ticket.event_type}
+
+
-
-
setSelectedTicket(ticket)}
- className="w-full rounded-2xl bg-zinc-800 py-4 font-bold text-white transition hover:bg-sky-500 hover:shadow-lg hover:shadow-sky-500/20"
+
+
+
- View Ticket QR
-
-
+
+
+
+ {formatDate(ticket.start_date)}
+
+
+
+
+ setSelectedTicket(ticket)}
+ className="w-full rounded-2xl bg-zinc-800 py-4 font-bold text-white transition hover:bg-sky-500 hover:shadow-lg hover:shadow-sky-500/20"
+ >
+ View Ticket QR
+
+
))}
diff --git a/soroban-client/app/verifier/page.tsx b/soroban-client/app/verifier/page.tsx
index 1bf168bd..5bef2d39 100644
--- a/soroban-client/app/verifier/page.tsx
+++ b/soroban-client/app/verifier/page.tsx
@@ -5,7 +5,14 @@ import Header from "@/components/Header";
import { Html5QrcodeScanner } from "html5-qrcode";
import { getBalance } from "@/lib/soroban";
const StellarSdk = require("@stellar/stellar-sdk");
-import { ShieldCheck, ShieldAlert, Loader2, Camera, User, Ticket } from "lucide-react";
+import {
+ ShieldCheck,
+ ShieldAlert,
+ Loader2,
+ Camera,
+ User,
+ Ticket,
+} from "lucide-react";
interface TicketData {
t: string; // token_id
@@ -18,7 +25,9 @@ interface TicketData {
export default function VerifierPage() {
const [scanResult, setScanResult] = useState
(null);
- const [verificationStatus, setVerificationStatus] = useState<"idle" | "verifying" | "valid" | "invalid">("idle");
+ const [verificationStatus, setVerificationStatus] = useState<
+ "idle" | "verifying" | "valid" | "invalid"
+ >("idle");
const [errorMsg, setErrorMsg] = useState("");
const [onChainValid, setOnChainValid] = useState(null);
const scannerRef = useRef(null);
@@ -27,7 +36,7 @@ export default function VerifierPage() {
const scanner = new Html5QrcodeScanner(
"reader",
{ fps: 10, qrbox: { width: 250, height: 250 } },
- /* verbose= */ false
+ /* verbose= */ false,
);
scanner.render(onScanSuccess, onScanFailure);
scannerRef.current = scanner;
@@ -66,7 +75,9 @@ export default function VerifierPage() {
try {
const keypair = StellarSdk.Keypair.fromPublicKey(data.o);
const messageBytes = new TextEncoder().encode(message);
- const sigBytes = Uint8Array.from(atob(data.sig!), c => c.charCodeAt(0));
+ const sigBytes = Uint8Array.from(atob(data.sig!), (c) =>
+ c.charCodeAt(0),
+ );
const isValidSig = keypair.verify(messageBytes, sigBytes);
if (!isValidSig) {
setVerificationStatus("invalid");
@@ -96,7 +107,6 @@ export default function VerifierPage() {
setErrorMsg("Could not verify on-chain (offline mode?)");
setVerificationStatus(data.sig ? "valid" : "invalid");
}
-
} catch (err) {
setVerificationStatus("invalid");
setErrorMsg("Verification process failed.");
@@ -116,105 +126,162 @@ export default function VerifierPage() {
-
- Organizer Mode
-
-
Ticket Verifier
-
- Scan participant QR codes to verify ticket ownership and authenticity via cryptographic signatures and on-chain records.
-
+
+ Organizer Mode
+
+
+ Ticket Verifier
+
+
+ Scan participant QR codes to verify ticket ownership and
+ authenticity via cryptographic signatures and on-chain records.
+
- {/* Scanner Section */}
-
-
- {verificationStatus !== "idle" && (
-
-
- Scan Next Ticket
-
+ {/* Scanner Section */}
+
+
+ {verificationStatus !== "idle" && (
+
+
+ Scan Next Ticket
+
+
+ )}
+
+
+ {/* Results Section */}
+
+ {verificationStatus === "idle" ? (
+
+
+
+
+
+ Waiting for scan...
+
+
+ ) : (
+
+
+
+ {verificationStatus === "verifying" ? (
+
+ ) : verificationStatus === "valid" ? (
+
+ ) : (
+
+ )}
+
+
+
+ Verification Status
+
+
+ {verificationStatus === "verifying"
+ ? "Analyzing..."
+ : verificationStatus === "valid"
+ ? "Access Granted"
+ : "Access Denied"}
+
+
- )}
-
-
- {/* Results Section */}
-
- {verificationStatus === "idle" ? (
-
-
-
-
-
Waiting for scan...
+
+ {errorMsg && (
+
+ {errorMsg}
+
+ )}
+
+
+
+
+
+
+
+
+ TICKET OWNER
+
+
+ {scanResult?.o}
+
+
+
+
+
+
+
+
+
+ EVENT / TOKEN
+
+
+ Event #{scanResult?.e} • Token #{scanResult?.t}
+
+
+
- ) : (
-
-
-
-
- {verificationStatus === "verifying" ? :
- verificationStatus === "valid" ? : }
-
-
-
Verification Status
-
- {verificationStatus === "verifying" ? "Analyzing..." :
- verificationStatus === "valid" ? "Access Granted" : "Access Denied"}
-
-
-
-
- {errorMsg && (
-
- {errorMsg}
-
- )}
-
-
-
-
-
- TICKET OWNER
- {scanResult?.o}
-
-
-
-
-
- EVENT / TOKEN
- Event #{scanResult?.e} • Token #{scanResult?.t}
-
-
-
-
-
-
-
On-Chain
-
{onChainValid === true ? "Confirmed" : onChainValid === false ? "Failed" : "Checking..."}
-
-
-
Signature
-
{scanResult?.sig ? "Verified" : "Missing"}
-
-
+
+
+
+
+ On-Chain
+
+
+ {onChainValid === true
+ ? "Confirmed"
+ : onChainValid === false
+ ? "Failed"
+ : "Checking..."}
+
+
+
+
+ Signature
+
+
+ {scanResult?.sig ? "Verified" : "Missing"}
+
+
- )}
-
+
+ )}
+
diff --git a/soroban-client/components/AboutSection.tsx b/soroban-client/components/AboutSection.tsx
index 384fe4a9..5679387a 100644
--- a/soroban-client/components/AboutSection.tsx
+++ b/soroban-client/components/AboutSection.tsx
@@ -9,16 +9,36 @@ export default function AboutSection() {
diff --git a/soroban-client/components/AnalyticsDashboard.tsx b/soroban-client/components/AnalyticsDashboard.tsx
index 27f03f50..817a9479 100644
--- a/soroban-client/components/AnalyticsDashboard.tsx
+++ b/soroban-client/components/AnalyticsDashboard.tsx
@@ -14,7 +14,7 @@ export default function AnalyticsDashboard() {
const snapshot = getAnalyticsSnapshot();
const totalPageViews = Object.values(snapshot.pageViews).reduce(
(sum, views) => sum + views,
- 0
+ 0,
);
const pageViewSeries =
@@ -45,7 +45,7 @@ export default function AnalyticsDashboard() {
const maxEventSales = Math.max(...eventSeries.map((item) => item.sold), 1);
const totalPlatformActions = platformBreakdown.reduce(
(sum, item) => sum + item.value,
- 0
+ 0,
);
return (
@@ -80,7 +80,9 @@ export default function AnalyticsDashboard() {
Usage Trends
-
Page traffic
+
+ Page traffic
+
Local analytics snapshot
@@ -111,7 +113,9 @@ export default function AnalyticsDashboard() {
Platform Mix
-
What users do most
+
+ What users do most
+
@@ -122,12 +126,18 @@ export default function AnalyticsDashboard() {
: (item.value / totalPlatformActions) * 100;
return (
-
+
{item.name}
@@ -155,7 +165,9 @@ export default function AnalyticsDashboard() {
Organizer Dashboard
-
Event performance
+
+ Event performance
+
@@ -165,7 +177,9 @@ export default function AnalyticsDashboard() {
className="rounded-2xl border border-white/10 bg-black/20 p-4"
>
-
{event.name}
+
+ {event.name}
+
{event.conversion}% conversion
@@ -190,7 +204,9 @@ export default function AnalyticsDashboard() {
Tickets by Event
-
Sales distribution
+
+ Sales distribution
+
@@ -229,7 +245,9 @@ function MetricCard({
}) {
return (
- {label}
+
+ {label}
+
{value}
{detail}
diff --git a/soroban-client/components/ContractEventFeed.tsx b/soroban-client/components/ContractEventFeed.tsx
index 232bb0d8..14ca5b6a 100644
--- a/soroban-client/components/ContractEventFeed.tsx
+++ b/soroban-client/components/ContractEventFeed.tsx
@@ -32,18 +32,21 @@ export default function ContractEventFeed() {
>("");
const [typeFilter, setTypeFilter] = useState
("");
- const { events, total, loading, error, updatedAt, refetch } = useContractEvents({
- status: statusFilter || undefined,
- type: typeFilter || undefined,
- limit: 50,
- realtime: true,
- });
+ const { events, total, loading, error, updatedAt, refetch } =
+ useContractEvents({
+ status: statusFilter || undefined,
+ type: typeFilter || undefined,
+ limit: 50,
+ realtime: true,
+ });
return (
-
Contract Event Feed
+
+ Contract Event Feed
+
{total} events indexed
{updatedAt > 0 && (
@@ -70,17 +73,17 @@ export default function ContractEventFeed() {
{/* Type filter */}
- setTypeFilter(e.target.value as typeof typeFilter)
- }
+ onChange={(e) => setTypeFilter(e.target.value as typeof typeFilter)}
className="rounded-lg border border-white/10 bg-[#2a2a2a] px-3 py-1.5 text-sm text-white focus:outline-none"
>
All types
- {(Object.keys(EVENT_TYPE_LABELS) as ContractEventType[]).map((t) => (
-
- {EVENT_TYPE_LABELS[t]}
-
- ))}
+ {(Object.keys(EVENT_TYPE_LABELS) as ContractEventType[]).map(
+ (t) => (
+
+ {EVENT_TYPE_LABELS[t]}
+
+ ),
+ )}
No events found. Make sure{" "}
- NEXT_PUBLIC_EVENT_MANAGER_CONTRACT is
- configured.
+
+ NEXT_PUBLIC_EVENT_MANAGER_CONTRACT
+ {" "}
+ is configured.
) : (
@@ -134,7 +139,9 @@ export default function ContractEventFeed() {
{ev.eventId ?? "—"}
-
+
{ev.status}
{ev.ledger}
diff --git a/soroban-client/components/EventCatalog.tsx b/soroban-client/components/EventCatalog.tsx
index 3728ed53..d3547d44 100644
--- a/soroban-client/components/EventCatalog.tsx
+++ b/soroban-client/components/EventCatalog.tsx
@@ -81,7 +81,9 @@ export default function EventCatalog() {
setEvents(list);
} catch (e: unknown) {
const msg =
- e instanceof Error ? e.message : "Could not load events from the network.";
+ e instanceof Error
+ ? e.message
+ : "Could not load events from the network.";
setLoadError(msg);
setEvents([]);
} finally {
@@ -146,7 +148,10 @@ export default function EventCatalog() {
const buyer = address || localStorage.getItem("wallet_address");
if (!buyer) {
- setFeedback({ type: "error", message: "Connect your wallet to continue." });
+ setFeedback({
+ type: "error",
+ message: "Connect your wallet to continue.",
+ });
return;
}
@@ -175,7 +180,7 @@ export default function EventCatalog() {
setPurchasePhase("confirming");
const result = await purchaseTickets(
{ buyer, eventId: selected.id, quantity: qty },
- signTransaction
+ signTransaction,
);
const unit = selected.ticket_price;
@@ -183,7 +188,7 @@ export default function EventCatalog() {
trackTicketPurchase(
selected.theme,
quantity,
- Number(revenueStroops) / STROOPS_PER_XLM
+ Number(revenueStroops) / STROOPS_PER_XLM,
);
setFeedback({
@@ -227,7 +232,10 @@ export default function EventCatalog() {
@@ -471,9 +483,9 @@ export default function EventCatalog() {
1,
Math.min(
Number(remainingTickets(selected)),
- Number(ev.target.value) || 1
- )
- )
+ Number(ev.target.value) || 1,
+ ),
+ ),
)
}
className="h-11 w-24 rounded-2xl border border-white/10 bg-zinc-950 text-center text-white"
@@ -483,10 +495,7 @@ export default function EventCatalog() {
className="flex h-11 w-11 items-center justify-center rounded-2xl border border-white/15 bg-white/5 text-xl text-white hover:bg-white/10"
onClick={() =>
setQuantity((q) =>
- Math.min(
- Number(remainingTickets(selected)),
- q + 1
- )
+ Math.min(Number(remainingTickets(selected)), q + 1),
)
}
aria-label="Increase quantity"
diff --git a/soroban-client/components/FeaturesSection.tsx b/soroban-client/components/FeaturesSection.tsx
index 1db27310..93d2ef1f 100644
--- a/soroban-client/components/FeaturesSection.tsx
+++ b/soroban-client/components/FeaturesSection.tsx
@@ -8,7 +8,12 @@ export default function FeaturesSection() {
{
key: "eventManagement",
icon: (
-
+
),
@@ -16,33 +21,73 @@ export default function FeaturesSection() {
{
key: "analytics",
icon: (
-
-
-
+
+
+
),
},
{
key: "marketplace",
icon: (
-
-
+
+
),
},
{
key: "security",
icon: (
-
-
+
+
),
},
{
key: "identity",
icon: (
-
-
+
+
),
},
diff --git a/soroban-client/components/Footer.tsx b/soroban-client/components/Footer.tsx
index 44415993..7f45023e 100644
--- a/soroban-client/components/Footer.tsx
+++ b/soroban-client/components/Footer.tsx
@@ -19,9 +19,13 @@ export default function Footer() {
-
CrowdPass
+
+ CrowdPass
+
-
{t("tagline")}
+
+ {t("tagline")}
+
@@ -76,23 +157,57 @@ export default function Footer() {
{[
-
,
+
,
<>
-
-
+
+
>,
<>
-
-
+
+
>,
<>
>,
].map((icon, i) => (
-
-
+
+
{icon}
diff --git a/soroban-client/components/Header.tsx b/soroban-client/components/Header.tsx
index d38607ea..dc5c1732 100644
--- a/soroban-client/components/Header.tsx
+++ b/soroban-client/components/Header.tsx
@@ -6,7 +6,7 @@ import { useEffect, useState } from "react";
import { Menu, X } from "lucide-react";
import { useWallet } from "@/contexts/WalletContext";
-import type { WalletProviderId } from '@/contexts/walletAdapters';
+import type { WalletProviderId } from "@/contexts/walletAdapters";
const NAV_LINKS = [
{ href: "/", label: "Home" },
@@ -161,13 +161,19 @@ export default function Header() {
aria-expanded={isMenuOpen}
aria-controls="mobile-menu"
>
- {isMenuOpen ? : }
+ {isMenuOpen ? (
+
+ ) : (
+
+ )}
Choose Wallet Provider
-
+
@@ -270,26 +279,33 @@ export default function Header() {
else window.open(provider.installUrl, "_blank");
}}
className={`w-full rounded-lg border p-3 text-left transition ${
- provider.id === providerId ? "border-blue-400" : "border-gray-600"
+ provider.id === providerId
+ ? "border-blue-400"
+ : "border-gray-600"
} ${provider.installed ? "hover:border-blue-300" : "cursor-pointer opacity-70"}`}
>
{provider.name}
-
{provider.description}
+
+ {provider.description}
+
+
+
+ {provider.installed ? "Available" : "Install"}
-
{provider.installed ? "Available" : "Install"}
))}
- Not installed? Click to open the official install page then refresh.
+ Not installed? Click to open the official install page then
+ refresh.
)}
);
-}
\ No newline at end of file
+}
diff --git a/soroban-client/components/Hero.tsx b/soroban-client/components/Hero.tsx
index 5e916b41..f2020bc3 100644
--- a/soroban-client/components/Hero.tsx
+++ b/soroban-client/components/Hero.tsx
@@ -13,8 +13,7 @@ export default function Hero() {
{t("tagline")}
- {t("headline")}{" "}
- {t("brand")}
+ {t("headline")} {t("brand")}
{t("cta")}
@@ -25,27 +24,77 @@ export default function Hero() {
-
+
-
+
-
+
-
+
-
-
-
+
+
+
diff --git a/soroban-client/components/HowItWorksSection.tsx b/soroban-client/components/HowItWorksSection.tsx
index 63631245..37bc65ab 100644
--- a/soroban-client/components/HowItWorksSection.tsx
+++ b/soroban-client/components/HowItWorksSection.tsx
@@ -10,8 +10,19 @@ export default function HowItWorksSection() {
id: 1,
key: "step1",
icon: (
-
-
+
+
),
active: true,
@@ -20,8 +31,19 @@ export default function HowItWorksSection() {
id: 2,
key: "step2",
icon: (
-
-
+
+
),
active: false,
@@ -30,8 +52,19 @@ export default function HowItWorksSection() {
id: 3,
key: "step3",
icon: (
-
-
+
+
),
active: false,
@@ -40,8 +73,19 @@ export default function HowItWorksSection() {
id: 4,
key: "step4",
icon: (
-
-
+
+
),
active: false,
@@ -58,7 +102,12 @@ export default function HowItWorksSection() {
@@ -67,18 +116,24 @@ export default function HowItWorksSection() {
{step.icon}
-
+
{t(`${step.key}.title`)}
{step.active && (
diff --git a/soroban-client/components/Marketplace/CreateListingForm.tsx b/soroban-client/components/Marketplace/CreateListingForm.tsx
index 1a22bd0a..481eada0 100644
--- a/soroban-client/components/Marketplace/CreateListingForm.tsx
+++ b/soroban-client/components/Marketplace/CreateListingForm.tsx
@@ -7,7 +7,9 @@ interface CreateListingFormProps {
onSuccess: () => void;
}
-export default function CreateListingForm({ onSuccess }: CreateListingFormProps) {
+export default function CreateListingForm({
+ onSuccess,
+}: CreateListingFormProps) {
const { address } = useWallet();
const [ticketContract, setTicketContract] = useState("");
const [tokenId, setTokenId] = useState("");
@@ -17,7 +19,7 @@ export default function CreateListingForm({ onSuccess }: CreateListingFormProps)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
-
+
if (!address) {
setError("Please connect your wallet first");
return;
@@ -44,7 +46,7 @@ export default function CreateListingForm({ onSuccess }: CreateListingFormProps)
});
const data = await response.json();
-
+
if (data.success) {
onSuccess();
setTicketContract("");
@@ -63,13 +65,19 @@ export default function CreateListingForm({ onSuccess }: CreateListingFormProps)
return (