Skip to content
Open
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 soroban-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-

## Getting Started

First, run the development server:
Before running the app, configure a few environment variables in `.env.local` (see example in the repo). At a minimum you should set:

```env
NEXT_PUBLIC_HORIZON_URL=https://horizon-testnet.stellar.org
NEXT_PUBLIC_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
NEXT_PUBLIC_EVENT_MANAGER_CONTRACT=C... # address of deployed EventManager contract
```

Once your env file is populated, start the development server:

```bash
npm run dev
Expand Down
22 changes: 13 additions & 9 deletions soroban-client/__tests__/components/Header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ describe('Header Component', () => {
render(<Header />);

expect(screen.getByText('CrowdPass')).toBeInTheDocument();
expect(screen.getByText('Events')).toBeInTheDocument();
expect(screen.getByText('Marketplace')).toBeInTheDocument();
expect(screen.getByText('Create Events')).toBeInTheDocument();
expect(screen.getByText('Connect Wallet')).toBeInTheDocument();
// Events, Marketplace, Create Events may appear multiple times (desktop + mobile)
expect(screen.getAllByText('Events').length).toBeGreaterThan(0);
expect(screen.getAllByText('Marketplace').length).toBeGreaterThan(0);
expect(screen.getAllByText('Create Events').length).toBeGreaterThan(0);
expect(screen.getAllByText('Connect Wallet').length).toBeGreaterThan(0);
});

it('displays Install Freighter if wallet is not installed', () => {
Expand All @@ -37,7 +38,8 @@ describe('Header Component', () => {
});

render(<Header />);
expect(screen.getByText('Install Freighter')).toBeInTheDocument();
// Install Freighter may appear multiple times (desktop + mobile)
expect(screen.getAllByText('Install Freighter').length).toBeGreaterThan(0);
});

it('renders connected wallet address prefix and disconnect button when connected', () => {
Expand All @@ -54,11 +56,13 @@ describe('Header Component', () => {

render(<Header />);
const formattedAddress = 'GBJ2...V2Y2';
expect(screen.getByText(formattedAddress)).toBeInTheDocument();
// There are multiple address displays (desktop and mobile), so verify they exist
expect(screen.getAllByText(formattedAddress).length).toBeGreaterThan(0);

const disconnectBtn = screen.getByText('Disconnect');
expect(disconnectBtn).toBeInTheDocument();
fireEvent.click(disconnectBtn);
// Disconnect buttons may appear multiple times (desktop + mobile)
const disconnectBtns = screen.getAllByText('Disconnect');
expect(disconnectBtns.length).toBeGreaterThan(0);
fireEvent.click(disconnectBtns[0]);
expect(disconnectMock).toHaveBeenCalledTimes(1);
});
});
221 changes: 221 additions & 0 deletions soroban-client/app/create-event/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
"use client";

import React, { useState } from "react";
import { useRouter } from "next/navigation";
import { useWallet } from "@/contexts/WalletContext";
import { createEvent } from "@/lib/soroban";

export default function CreateEventPage() {
const router = useRouter();
const { address, isConnected, isInstalled, connect } = useWallet();

Check warning on line 10 in soroban-client/app/create-event/page.tsx

View workflow job for this annotation

GitHub Actions / Next.js Client CI

'isConnected' is assigned a value but never used

const [theme, setTheme] = useState("");
const [description, setDescription] = useState("");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const [price, setPrice] = useState("");
const [tickets, setTickets] = useState("");
const [image, setImage] = useState<File | null>(null);

Check warning on line 18 in soroban-client/app/create-event/page.tsx

View workflow job for this annotation

GitHub Actions / Next.js Client CI

'image' is assigned a value but never used

const [errors, setErrors] = useState<{ [key: string]: string }>({});
const [submitting, setSubmitting] = useState(false);
const [successMsg, setSuccessMsg] = useState("");
const [errorMsg, setErrorMsg] = useState("");

const validate = () => {
const errs: { [key: string]: string } = {};
const now = Date.now();

if (!theme.trim()) errs.theme = "Event name required";
if (!startDate) errs.startDate = "Start date is required";
if (!endDate) errs.endDate = "End date is required";
if (startDate && new Date(startDate).getTime() <= now)
errs.startDate = "Start date must be in the future";
if (startDate && endDate && new Date(endDate) <= new Date(startDate))
errs.endDate = "End date must be after start date";
if (!price) errs.price = "Price required";
if (price && isNaN(Number(price))) errs.price = "Price must be a number";
if (price && Number(price) < 0) errs.price = "Price cannot be negative";
if (!tickets) errs.tickets = "Total tickets required";
if (tickets && (!/^[0-9]+$/.test(tickets) || Number(tickets) <= 0))
errs.tickets = "Must be a positive integer";

return errs;
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!address) {
if (isInstalled) {
await connect();
} else {
alert("Please install Freighter to create an event.");
return;
}
}
const errs = validate();
if (Object.keys(errs).length) {
setErrors(errs);
return;
}
setErrors({});
setSubmitting(true);
setErrorMsg("");
setSuccessMsg("");

try {
const organizer = address!;
const startUnix = Math.floor(new Date(startDate).getTime() / 1000);
const endUnix = Math.floor(new Date(endDate).getTime() / 1000);
const ticketPrice = BigInt(Math.floor(parseFloat(price) * 1_000_000));
const totalTickets = BigInt(tickets);

// for simplicity we use zero address as payment token; replace with real
// token contract address or allow user selection later.
const paymentToken = "0000000000000000000000000000000000000000000000000000000000000000";

const res = await createEvent({
organizer,
theme,
eventType: description,
startTimeUnix: startUnix,
endTimeUnix: endUnix,
ticketPrice,
totalTickets,
paymentToken,
});

console.log("transaction result", res);
setSuccessMsg("Event created (tx " + res.hash + ")");
// Optionally redirect to dashboard or home after creation
setTimeout(() => router.push("/"), 3000);
} catch (err: any) {

Check failure on line 92 in soroban-client/app/create-event/page.tsx

View workflow job for this annotation

GitHub Actions / Next.js Client CI

Unexpected any. Specify a different type
console.error(err);
setErrorMsg(err.message || "unknown error");
} finally {
setSubmitting(false);
}
};

return (
<div className="max-w-xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-4">Create Event</h1>

{successMsg && (
<div className="bg-green-100 text-green-800 p-2 mb-4">{successMsg}</div>
)}
{errorMsg && (
<div className="bg-red-100 text-red-800 p-2 mb-4">{errorMsg}</div>
)}

<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">
Event Name
</label>
<input
type="text"
value={theme}
onChange={(e) => setTheme(e.target.value)}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm"
/>
{errors.theme && (
<p className="text-red-600 text-sm">{errors.theme}</p>
)}
</div>

<div>
<label className="block text-sm font-medium text-gray-700">
Description
</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm"
rows={3}
/>
</div>

<div>
<label className="block text-sm font-medium text-gray-700">
Start Date &amp; Time
</label>
<input
type="datetime-local"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm"
/>
{errors.startDate && (
<p className="text-red-600 text-sm">{errors.startDate}</p>
)}
</div>

<div>
<label className="block text-sm font-medium text-gray-700">
End Date &amp; Time
</label>
<input
type="datetime-local"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm"
/>
{errors.endDate && (
<p className="text-red-600 text-sm">{errors.endDate}</p>
)}
</div>

<div>
<label className="block text-sm font-medium text-gray-700">
Ticket Price (XLM)
</label>
<input
type="number"
step="0.000001"
value={price}
onChange={(e) => setPrice(e.target.value)}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm"
/>
{errors.price && (
<p className="text-red-600 text-sm">{errors.price}</p>
)}
</div>

<div>
<label className="block text-sm font-medium text-gray-700">
Total Tickets
</label>
<input
type="number"
value={tickets}
onChange={(e) => setTickets(e.target.value)}
className="mt-1 block w-full border-gray-300 rounded-md shadow-sm"
/>
{errors.tickets && (
<p className="text-red-600 text-sm">{errors.tickets}</p>
)}
</div>

<div>
<label className="block text-sm font-medium text-gray-700">
Event Image (optional)
</label>
<input
type="file"
accept="image/*"
onChange={(e) => setImage(e.target.files?.[0] || null)}
/>
</div>

<button
type="submit"
disabled={submitting}
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md disabled:opacity-50"
>
{submitting ? "Creating..." : "Create Event"}
</button>
</form>
</div>
);
}
2 changes: 1 addition & 1 deletion soroban-client/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function Home() {
return (
<div className="bg-[#18181B] min-h-screen text-white font-sans selection:bg-[#FF5722] selection:text-white flex flex-col">
<Header />
<main className="flex-grow flex flex-col">
<main className="grow flex flex-col">
<Hero />
<AboutSection />
<FeaturesSection />
Expand Down
16 changes: 12 additions & 4 deletions soroban-client/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ export default function Header() {
</button>
)}

<button className="bg-[#FF5722] hover:bg-[#F4511E] text-white px-6 py-2 rounded-lg font-bold shadow-md transition">
<Link href="/create-event" className="bg-[#FF5722] hover:bg-[#F4511E] text-white px-6 py-2 rounded-lg font-bold shadow-md transition">
Create Events
</button>
</Link>
</div>

{/* Mobile Hamburger Button */}
Expand Down Expand Up @@ -188,6 +188,13 @@ export default function Header() {
>
Marketplace
</Link>
<Link
href="/create-event"
className="block text-gray-200 hover:text-white font-medium text-lg transition py-2"
onClick={handleMenuItemClick}
>
Create Event
</Link>
</nav>

{/* Divider */}
Expand Down Expand Up @@ -224,12 +231,13 @@ export default function Header() {
</button>
)}

<button
<Link
href="/create-event"
onClick={handleMenuItemClick}
className="w-full bg-[#FF5722] hover:bg-[#F4511E] text-white px-4 py-3 rounded-lg font-bold shadow-md transition"
>
Create Events
</button>
</Link>
</div>
</div>
</nav>
Expand Down
Loading
Loading