- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
383 lines
13 KiB
TypeScript
383 lines
13 KiB
TypeScript
import React, { useState } from 'react';
|
|
|
|
import { MOCK_USERS } from '../types/auth';
|
|
import {
|
|
MOCK_EVENTS,
|
|
MOCK_TICKET_TYPES,
|
|
DEFAULT_FEE_STRUCTURE,
|
|
type EventLite
|
|
} from '../types/business';
|
|
|
|
|
|
import { FeeBreakdown } from './billing';
|
|
import { OrderSummary } from './checkout';
|
|
import { EventCard } from './events';
|
|
import { MainContainer } from './layout';
|
|
import { ScanStatusBadge } from './scanning';
|
|
import { TicketTypeRow } from './tickets';
|
|
import { Button } from './ui/Button';
|
|
import { Card, CardHeader, CardBody } from './ui/Card';
|
|
|
|
import type { Order, ScanStatus } from '../types/business';
|
|
|
|
const DomainShowcase: React.FC = () => {
|
|
const [currentUser] = useState(MOCK_USERS[1]); // Organizer user
|
|
|
|
// Create example EventLite objects from MOCK_EVENTS
|
|
const exampleEvents: EventLite[] = MOCK_EVENTS.map((event) => ({
|
|
id: event.id,
|
|
orgId: event.organizationId,
|
|
name: event.title,
|
|
startAt: event.date,
|
|
endAt: event.date, // Using same date for end time since MOCK_EVENTS doesn't have endAt
|
|
venue: event.venue,
|
|
territoryId: event.territoryId,
|
|
status: event.status === 'published' ? 'published' : event.status === 'draft' ? 'draft' : 'archived'
|
|
}));
|
|
const [scanStatuses, setScanStatuses] = useState<ScanStatus[]>([
|
|
{
|
|
isValid: true,
|
|
status: 'valid',
|
|
timestamp: new Date().toISOString(),
|
|
ticketInfo: {
|
|
eventTitle: 'Autumn Gala & Silent Auction',
|
|
ticketTypeName: 'VIP Patron',
|
|
customerEmail: 'customer@example.com',
|
|
seatNumber: 'A12'
|
|
}
|
|
},
|
|
{
|
|
isValid: false,
|
|
status: 'used',
|
|
timestamp: new Date(Date.now() - 300000).toISOString(),
|
|
errorMessage: 'This ticket was already scanned 5 minutes ago'
|
|
},
|
|
{
|
|
isValid: false,
|
|
status: 'invalid',
|
|
errorMessage: 'QR code format is not recognized'
|
|
}
|
|
]);
|
|
|
|
// Mock order data
|
|
const mockOrder: Order = {
|
|
id: 'ord-123',
|
|
eventId: 'evt-1',
|
|
customerEmail: 'customer@example.com',
|
|
items: [
|
|
{
|
|
ticketTypeId: 'tt-1',
|
|
ticketTypeName: 'VIP Patron',
|
|
price: 35000,
|
|
quantity: 2,
|
|
subtotal: 70000
|
|
},
|
|
{
|
|
ticketTypeId: 'tt-2',
|
|
ticketTypeName: 'General Admission',
|
|
price: 15000,
|
|
quantity: 1,
|
|
subtotal: 15000
|
|
}
|
|
],
|
|
subtotal: 85000,
|
|
platformFee: 3075,
|
|
processingFee: 2495,
|
|
tax: 7875,
|
|
total: 98445,
|
|
status: 'pending',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
const handleEventAction = (_action: string, _eventId: string) => {
|
|
// Handle event actions in real application
|
|
};
|
|
|
|
const handleTicketAction = (_action: string, _ticketTypeId: string, _value?: unknown) => {
|
|
// Handle ticket type actions in real application
|
|
};
|
|
|
|
const handlePromoCode = async (_code: string) => {
|
|
// Apply promo code in real application
|
|
// Simulate API call
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
return { success: false, error: 'Promo code not found' };
|
|
};
|
|
|
|
const simulateScan = () => {
|
|
const isValid = Math.random() > 0.5;
|
|
const newStatus: ScanStatus = {
|
|
isValid,
|
|
status: Math.random() > 0.7 ? 'valid' : 'invalid',
|
|
timestamp: new Date().toISOString(),
|
|
...(isValid ? {} : { errorMessage: 'Invalid QR format' }),
|
|
ticketInfo: {
|
|
eventTitle: 'Contemporary Dance Showcase',
|
|
ticketTypeName: 'General Admission',
|
|
customerEmail: 'test@example.com'
|
|
}
|
|
};
|
|
setScanStatuses(prev => [newStatus, ...prev.slice(0, 4)]);
|
|
};
|
|
|
|
return (
|
|
<MainContainer>
|
|
<div className="space-y-8">
|
|
<div className="text-center">
|
|
<h1 className="text-3xl font-bold text-fg-primary mb-4">
|
|
Domain Components Showcase
|
|
</h1>
|
|
<p className="text-lg text-fg-secondary max-w-2xl mx-auto">
|
|
Professional event ticketing components for upscale venues with glassmorphism design
|
|
</p>
|
|
</div>
|
|
|
|
{/* Event Cards Section */}
|
|
<section className="space-y-4">
|
|
<h2 className="text-2xl font-semibold text-fg-primary">Event Cards</h2>
|
|
<p className="text-fg-secondary">
|
|
Display event information with role-based actions and glassmorphism styling
|
|
</p>
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{exampleEvents.map((event) => (
|
|
<EventCard
|
|
key={event.id}
|
|
event={event}
|
|
onHover={(eventId: string) => handleEventAction('hover', eventId)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</section>
|
|
|
|
{/* Ticket Type Management Section */}
|
|
<section className="space-y-4">
|
|
<h2 className="text-2xl font-semibold text-fg-primary">Ticket Type Management</h2>
|
|
<p className="text-fg-secondary">
|
|
Manage ticket types with inline editing and inventory tracking
|
|
</p>
|
|
|
|
{/* Card Layout */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-medium text-fg-primary">Card Layout (Mobile-Friendly)</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{MOCK_TICKET_TYPES.map((ticketType) => (
|
|
<TicketTypeRow
|
|
key={ticketType.id}
|
|
ticketType={ticketType}
|
|
layout="card"
|
|
{...(currentUser && { currentUser })}
|
|
onEdit={(tt) => handleTicketAction('edit', tt.id)}
|
|
onDelete={(id) => handleTicketAction('delete', id)}
|
|
onToggleStatus={(id, status) => handleTicketAction('toggle-status', id, status)}
|
|
onQuantityUpdate={(id, quantity) => handleTicketAction('quantity-update', id, quantity)}
|
|
onPriceUpdate={(id, price) => handleTicketAction('price-update', id, price)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Table Layout */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-medium text-fg-primary">Table Layout (Desktop)</h3>
|
|
<Card variant="elevated">
|
|
<CardBody className="p-0">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="border-b border-border-subtle">
|
|
<tr>
|
|
<th className="px-4 py-3 text-left text-sm font-semibold text-fg-primary">Ticket Type</th>
|
|
<th className="px-4 py-3 text-left text-sm font-semibold text-fg-primary">Price</th>
|
|
<th className="px-4 py-3 text-left text-sm font-semibold text-fg-primary">Quantity</th>
|
|
<th className="px-4 py-3 text-left text-sm font-semibold text-fg-primary">Sold</th>
|
|
<th className="px-4 py-3 text-left text-sm font-semibold text-fg-primary">Available</th>
|
|
<th className="px-4 py-3 text-left text-sm font-semibold text-fg-primary">Sales Rate</th>
|
|
<th className="px-4 py-3 text-left text-sm font-semibold text-fg-primary">Revenue</th>
|
|
<th className="px-4 py-3 text-left text-sm font-semibold text-fg-primary">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{MOCK_TICKET_TYPES.map((ticketType) => (
|
|
<TicketTypeRow
|
|
key={ticketType.id}
|
|
ticketType={ticketType}
|
|
layout="table"
|
|
{...(currentUser && { currentUser })}
|
|
onEdit={(tt) => handleTicketAction('edit', tt.id)}
|
|
onDelete={(id) => handleTicketAction('delete', id)}
|
|
onToggleStatus={(id, status) => handleTicketAction('toggle-status', id, status)}
|
|
onQuantityUpdate={(id, quantity) => handleTicketAction('quantity-update', id, quantity)}
|
|
onPriceUpdate={(id, price) => handleTicketAction('price-update', id, price)}
|
|
/>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</CardBody>
|
|
</Card>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Order Summary & Fee Breakdown Section */}
|
|
<section className="space-y-4">
|
|
<h2 className="text-2xl font-semibold text-fg-primary">Checkout Experience</h2>
|
|
<p className="text-fg-secondary">
|
|
Professional order summary with transparent fee breakdown
|
|
</p>
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Order Summary */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-medium text-fg-primary">Order Summary</h3>
|
|
<OrderSummary
|
|
order={mockOrder}
|
|
onPromoCodeApply={handlePromoCode}
|
|
onPromoCodeRemove={() => { /* Remove promo code */ }}
|
|
/>
|
|
|
|
<h4 className="text-md font-medium text-fg-primary">Compact Layout</h4>
|
|
<OrderSummary
|
|
order={mockOrder}
|
|
layout="compact"
|
|
showPromoCode={false}
|
|
/>
|
|
</div>
|
|
|
|
{/* Fee Breakdown */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-medium text-fg-primary">Fee Breakdown</h3>
|
|
<FeeBreakdown
|
|
order={mockOrder}
|
|
feeStructure={DEFAULT_FEE_STRUCTURE}
|
|
showTooltips
|
|
showCalculations={false}
|
|
/>
|
|
|
|
<h4 className="text-md font-medium text-fg-primary">Table Layout</h4>
|
|
<FeeBreakdown
|
|
order={mockOrder}
|
|
layout="table"
|
|
showCalculations
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Scanning Interface Section */}
|
|
<section className="space-y-4">
|
|
<h2 className="text-2xl font-semibold text-fg-primary">QR Scanning Interface</h2>
|
|
<p className="text-fg-secondary">
|
|
Real-time ticket validation with status indicators and animations
|
|
</p>
|
|
|
|
<div className="flex justify-center mb-6">
|
|
<Button onClick={simulateScan} variant="primary">
|
|
Simulate Ticket Scan
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
{scanStatuses.map((status, index) => (
|
|
<Card key={index} variant="elevated">
|
|
<CardHeader>
|
|
<h4 className="text-md font-medium text-fg-primary">
|
|
Scan Result #{scanStatuses.length - index}
|
|
</h4>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<ScanStatusBadge
|
|
scanStatus={status}
|
|
showTimestamp
|
|
showTicketInfo
|
|
animated
|
|
size="md"
|
|
/>
|
|
</CardBody>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
{/* Different Sizes */}
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-medium text-fg-primary">Badge Sizes</h3>
|
|
<div className="flex flex-wrap gap-4 items-center">
|
|
<ScanStatusBadge
|
|
scanStatus={scanStatuses[0]!}
|
|
size="sm"
|
|
showTimestamp={false}
|
|
showTicketInfo={false}
|
|
animated={false}
|
|
/>
|
|
<ScanStatusBadge
|
|
scanStatus={scanStatuses[0]!}
|
|
size="md"
|
|
showTimestamp={false}
|
|
showTicketInfo={false}
|
|
animated={false}
|
|
/>
|
|
<ScanStatusBadge
|
|
scanStatus={scanStatuses[0]!}
|
|
size="lg"
|
|
showTimestamp={false}
|
|
showTicketInfo={false}
|
|
animated={false}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Usage Examples */}
|
|
<section className="space-y-4">
|
|
<h2 className="text-2xl font-semibold text-fg-primary">Usage Examples</h2>
|
|
<Card variant="elevated">
|
|
<CardHeader>
|
|
<h3 className="text-lg font-semibold text-fg-primary">Integration Code</h3>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<pre className="text-sm text-fg-secondary bg-surface-secondary rounded p-4 overflow-x-auto">
|
|
{`import { EventCard, TicketTypeRow, OrderSummary, ScanStatusBadge, FeeBreakdown } from '../components';
|
|
|
|
// Event listing page
|
|
<EventCard
|
|
event={event}
|
|
currentUser={user}
|
|
onView={handleView}
|
|
onManage={handleManage}
|
|
/>
|
|
|
|
// Ticket management
|
|
<TicketTypeRow
|
|
ticketType={ticketType}
|
|
layout="card"
|
|
onEdit={handleEdit}
|
|
onQuantityUpdate={handleQuantityUpdate}
|
|
/>
|
|
|
|
// Checkout process
|
|
<OrderSummary
|
|
order={order}
|
|
onPromoCodeApply={handlePromoCode}
|
|
/>
|
|
|
|
// QR scanning
|
|
<ScanStatusBadge
|
|
scanStatus={scanResult}
|
|
animated={true}
|
|
showTicketInfo={true}
|
|
/>
|
|
|
|
// Admin fee transparency
|
|
<FeeBreakdown
|
|
order={order}
|
|
layout="table"
|
|
showCalculations={true}
|
|
/>`}
|
|
</pre>
|
|
</CardBody>
|
|
</Card>
|
|
</section>
|
|
</div>
|
|
</MainContainer>
|
|
);
|
|
};
|
|
|
|
export default DomainShowcase; |