feat(config): enhance development experience with strict linting and types
- Update ESLint configuration with strict React/TypeScript rules - Enhance TypeScript configuration with stricter checks - Add comprehensive type definitions and exports - Update App.tsx with new routing and layout integration - Create showcase pages for component development - Improve package.json with proper dependencies Configuration ensures code quality and developer productivity with zero-tolerance for type errors and consistent code standards. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -79,11 +79,11 @@ export default tseslint.config(
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// React Rules
|
||||
// React Rules - Strict Configuration
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
...jsxA11y.configs.recommended.rules,
|
||||
...jsxA11y.configs.strict.rules,
|
||||
|
||||
// React Refresh
|
||||
'react-refresh/only-export-components': [
|
||||
@@ -122,11 +122,27 @@ export default tseslint.config(
|
||||
},
|
||||
],
|
||||
'react/jsx-pascal-case': ['error', { allowAllCaps: true }],
|
||||
'react/no-array-index-key': 'off', // Allow index keys for static lists
|
||||
'react/no-array-index-key': 'warn', // Warn about index keys - use stable IDs when possible
|
||||
'react/no-danger': 'error',
|
||||
'react/no-deprecated': 'error',
|
||||
'react/no-unsafe': 'error',
|
||||
'react/hook-use-state': 'warn',
|
||||
'react-hooks/exhaustive-deps': 'error', // Strict enforcement of exhaustive deps
|
||||
'react/display-name': 'error', // All components must have display names
|
||||
|
||||
// Accessibility - Additional Strict Rules
|
||||
'jsx-a11y/no-autofocus': ['error', { ignoreNonDOM: true }],
|
||||
'jsx-a11y/anchor-is-valid': 'error',
|
||||
'jsx-a11y/click-events-have-key-events': 'error',
|
||||
'jsx-a11y/interactive-supports-focus': 'error',
|
||||
'jsx-a11y/label-has-associated-control': 'error',
|
||||
'jsx-a11y/media-has-caption': 'error',
|
||||
'jsx-a11y/no-static-element-interactions': 'error',
|
||||
'jsx-a11y/role-has-required-aria-props': 'error',
|
||||
'jsx-a11y/role-supports-aria-props': 'error',
|
||||
'jsx-a11y/scope': 'error',
|
||||
'jsx-a11y/heading-has-content': 'error',
|
||||
'jsx-a11y/img-redundant-alt': 'error',
|
||||
|
||||
// TypeScript Rules
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
@@ -148,12 +164,22 @@ export default tseslint.config(
|
||||
'@typescript-eslint/no-unused-expressions': 'error',
|
||||
'@typescript-eslint/prefer-readonly': 'warn',
|
||||
'@typescript-eslint/explicit-function-return-type': [
|
||||
'off', // Let TypeScript infer return types for React components
|
||||
'warn', // Enforce explicit return types for better documentation
|
||||
{
|
||||
allowExpressions: true,
|
||||
allowTypedFunctionExpressions: true,
|
||||
allowHigherOrderFunctions: true,
|
||||
allowDirectConstAssertionInArrowFunctions: true,
|
||||
allowConciseArrowFunctionExpressionsStartingWithVoid: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/explicit-module-boundary-types': [
|
||||
'warn',
|
||||
{
|
||||
allowArgumentsExplicitlyTypedAsAny: false,
|
||||
allowDirectConstAssertionInArrowFunctions: true,
|
||||
allowHigherOrderFunctions: true,
|
||||
allowTypedFunctionExpressions: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
@@ -216,8 +242,11 @@ export default tseslint.config(
|
||||
'import/default': 'off', // React 18 JSX transform doesn't export default
|
||||
'import/export': 'error',
|
||||
|
||||
// General Code Quality
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
// General Code Quality - Strict Rules
|
||||
'no-console': process.env.NODE_ENV === 'production'
|
||||
? ['error', { allow: ['warn', 'error'] }]
|
||||
: ['warn', { allow: ['warn', 'error', 'info'] }],
|
||||
'prefer-const': 'error',
|
||||
'no-debugger': 'error',
|
||||
'no-alert': 'error',
|
||||
'no-var': 'error',
|
||||
@@ -248,7 +277,7 @@ export default tseslint.config(
|
||||
'no-async-promise-executor': 'error',
|
||||
'no-await-in-loop': 'warn',
|
||||
|
||||
// Naming Conventions
|
||||
// Naming Conventions - Strict Standards
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'error',
|
||||
{
|
||||
@@ -268,6 +297,15 @@ export default tseslint.config(
|
||||
selector: 'interface',
|
||||
format: ['PascalCase'],
|
||||
},
|
||||
{
|
||||
selector: 'interface',
|
||||
filter: {
|
||||
regex: '^.*Props$',
|
||||
match: true,
|
||||
},
|
||||
format: ['PascalCase'],
|
||||
suffix: ['Props'],
|
||||
},
|
||||
{
|
||||
selector: 'typeAlias',
|
||||
format: ['PascalCase'],
|
||||
@@ -276,6 +314,10 @@ export default tseslint.config(
|
||||
selector: 'enum',
|
||||
format: ['PascalCase'],
|
||||
},
|
||||
{
|
||||
selector: 'enumMember',
|
||||
format: ['UPPER_CASE'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -320,11 +362,11 @@ export default tseslint.config(
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// React Rules
|
||||
// React Rules - Strict Configuration
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
...jsxA11y.configs.recommended.rules,
|
||||
...jsxA11y.configs.strict.rules,
|
||||
|
||||
// React Refresh
|
||||
'react-refresh/only-export-components': [
|
||||
@@ -355,13 +397,16 @@ export default tseslint.config(
|
||||
},
|
||||
],
|
||||
'react/jsx-pascal-case': ['error', { allowAllCaps: true }],
|
||||
'react/no-array-index-key': 'off', // Allow index keys for static lists
|
||||
'react/no-array-index-key': 'warn', // Warn about index keys - use stable IDs when possible
|
||||
'react/no-danger': 'error',
|
||||
'react/no-deprecated': 'error',
|
||||
'react/no-unsafe': 'error',
|
||||
|
||||
// General Code Quality
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
// General Code Quality - Strict Rules
|
||||
'no-console': process.env.NODE_ENV === 'production'
|
||||
? ['error', { allow: ['warn', 'error'] }]
|
||||
: ['warn', { allow: ['warn', 'error', 'info'] }],
|
||||
'prefer-const': 'error',
|
||||
'no-debugger': 'error',
|
||||
'no-alert': 'error',
|
||||
'no-var': 'error',
|
||||
|
||||
25
reactrebuild0825/package-lock.json
generated
25
reactrebuild0825/package-lock.json
generated
@@ -45,6 +45,7 @@
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"vite": "^6.0.1"
|
||||
}
|
||||
},
|
||||
@@ -7158,6 +7159,30 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.39.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz",
|
||||
"integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.39.1",
|
||||
"@typescript-eslint/parser": "8.39.1",
|
||||
"@typescript-eslint/typescript-estree": "8.39.1",
|
||||
"@typescript-eslint/utils": "8.39.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
||||
|
||||
@@ -18,7 +18,17 @@
|
||||
"quality:fix": "npm run typecheck && npm run lint:fix && npm run format",
|
||||
"test": "playwright test",
|
||||
"test:ui": "playwright test --ui",
|
||||
"test:headed": "playwright test --headed"
|
||||
"test:headed": "playwright test --headed",
|
||||
"test:qa": "tsx tests/test-runner.ts",
|
||||
"test:qa:critical": "tsx tests/test-runner.ts --critical",
|
||||
"test:qa:headed": "tsx tests/test-runner.ts --headed",
|
||||
"test:smoke": "playwright test tests/smoke.spec.ts",
|
||||
"test:auth": "playwright test tests/auth-realistic.spec.ts",
|
||||
"test:auth:enhanced": "playwright test tests/auth.spec.ts",
|
||||
"test:navigation": "playwright test tests/navigation.spec.ts",
|
||||
"test:theme": "playwright test tests/theme.spec.ts",
|
||||
"test:responsive": "playwright test tests/responsive.spec.ts",
|
||||
"test:components": "playwright test tests/components.spec.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
@@ -36,6 +46,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.54.2",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.39.1",
|
||||
@@ -56,7 +67,9 @@
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.39.1",
|
||||
"vite": "^6.0.1"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
@@ -1,24 +1,130 @@
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
|
||||
import { ProtectedRoute, AdminRoute } from './components/auth/ProtectedRoute';
|
||||
import { AppErrorBoundary } from './components/errors/AppErrorBoundary';
|
||||
import { GlassShowcase } from './components/GlassShowcase';
|
||||
import { AppLayout } from './components/layout/AppLayout';
|
||||
import { RouteSuspense } from './components/loading/RouteSuspense';
|
||||
import { ThemeDocumentation } from './components/ThemeDocumentation';
|
||||
import { AuthProvider } from './contexts/AuthContext';
|
||||
import { DashboardPage } from './pages/DashboardPage';
|
||||
import {
|
||||
ErrorPage,
|
||||
NotFoundPage,
|
||||
UnauthorizedPage,
|
||||
ServerErrorPage,
|
||||
NetworkErrorPage
|
||||
} from './pages/ErrorPage';
|
||||
import { EventsPage } from './pages/EventsPage';
|
||||
import { HomePage } from './pages/HomePage';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
|
||||
function App() {
|
||||
function App(): JSX.Element {
|
||||
return (
|
||||
<AppErrorBoundary>
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<div className='bg-premium-dark min-h-screen'>
|
||||
<RouteSuspense
|
||||
skeletonType="page"
|
||||
loadingText="Loading application..."
|
||||
timeout={15000}
|
||||
>
|
||||
<Routes>
|
||||
<Route path='/' element={<HomePage />} />
|
||||
<Route path='/dashboard' element={<DashboardPage />} />
|
||||
<Route path='/events' element={<EventsPage />} />
|
||||
{/* Public routes without authentication */}
|
||||
<Route path='/login' element={<LoginPage />} />
|
||||
<Route path='/home' element={<HomePage />} />
|
||||
<Route path='/showcase' element={<GlassShowcase />} />
|
||||
<Route path='/docs' element={<ThemeDocumentation />} />
|
||||
</Routes>
|
||||
|
||||
{/* Protected routes with layout */}
|
||||
<Route path='/' element={
|
||||
<ProtectedRoute>
|
||||
<AppLayout title="Dashboard" subtitle="Overview of your events and performance">
|
||||
<DashboardPage />
|
||||
</AppLayout>
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/dashboard' element={
|
||||
<ProtectedRoute>
|
||||
<AppLayout title="Dashboard" subtitle="Overview of your events and performance">
|
||||
<DashboardPage />
|
||||
</AppLayout>
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/events' element={
|
||||
<ProtectedRoute permissions={['events:read']}>
|
||||
<AppLayout title="Events" subtitle="Manage your upcoming events">
|
||||
<EventsPage />
|
||||
</AppLayout>
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/tickets' element={
|
||||
<ProtectedRoute permissions={['tickets:read']}>
|
||||
<AppLayout title="Tickets" subtitle="Track ticket sales and manage inventory">
|
||||
<div className="text-slate-900 dark:text-slate-100">
|
||||
<h2 className="text-xl font-semibold mb-4">Tickets Management</h2>
|
||||
<p>Ticket management functionality coming soon...</p>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/customers' element={
|
||||
<ProtectedRoute permissions={['customers:read']}>
|
||||
<AppLayout title="Customers" subtitle="View and manage customer information">
|
||||
<div className="text-slate-900 dark:text-slate-100">
|
||||
<h2 className="text-xl font-semibold mb-4">Customer Management</h2>
|
||||
<p>Customer management functionality coming soon...</p>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/analytics' element={
|
||||
<ProtectedRoute permissions={['analytics:read']}>
|
||||
<AppLayout title="Analytics" subtitle="View detailed performance metrics">
|
||||
<div className="text-slate-900 dark:text-slate-100">
|
||||
<h2 className="text-xl font-semibold mb-4">Analytics Dashboard</h2>
|
||||
<p>Analytics dashboard coming soon...</p>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path='/settings' element={
|
||||
<ProtectedRoute>
|
||||
<AppLayout title="Settings" subtitle="Configure your account and preferences">
|
||||
<div className="text-slate-900 dark:text-slate-100">
|
||||
<h2 className="text-xl font-semibold mb-4">Account Settings</h2>
|
||||
<p>Settings page coming soon...</p>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
{/* Admin routes */}
|
||||
<Route path='/admin/*' element={
|
||||
<AdminRoute>
|
||||
<AppLayout title="Admin" subtitle="Platform administration">
|
||||
<div className="text-slate-900 dark:text-slate-100">
|
||||
<h2 className="text-xl font-semibold mb-4">Admin Panel</h2>
|
||||
<p>Admin functionality coming soon...</p>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</AdminRoute>
|
||||
} />
|
||||
|
||||
{/* Error routes */}
|
||||
<Route path='/unauthorized' element={<UnauthorizedPage />} />
|
||||
<Route path='/error/network' element={<NetworkErrorPage />} />
|
||||
<Route path='/error/server' element={<ServerErrorPage />} />
|
||||
<Route path='/error/timeout' element={<NetworkErrorPage />} />
|
||||
<Route path='/error' element={<ErrorPage />} />
|
||||
|
||||
{/* 404 catch-all route - must be last */}
|
||||
<Route path='*' element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</RouteSuspense>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
</AppErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
265
reactrebuild0825/src/components/UIShowcase.tsx
Normal file
265
reactrebuild0825/src/components/UIShowcase.tsx
Normal file
@@ -0,0 +1,265 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Select,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
Badge,
|
||||
Alert,
|
||||
type SelectOption
|
||||
} from './ui';
|
||||
|
||||
export function UIShowcase() {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [selectValue, setSelectValue] = useState('');
|
||||
const [showAlert, setShowAlert] = useState(true);
|
||||
|
||||
const selectOptions: SelectOption[] = [
|
||||
{ value: 'option1', label: 'First Option' },
|
||||
{ value: 'option2', label: 'Second Option' },
|
||||
{ value: 'option3', label: 'Third Option' },
|
||||
{ value: 'option4', label: 'Disabled Option', disabled: true },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background-primary p-6">
|
||||
<div className="max-w-6xl mx-auto space-y-8">
|
||||
{/* Header */}
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-4xl font-bold text-text-primary">UI Component Showcase</h1>
|
||||
<p className="text-text-secondary">
|
||||
Premium glassmorphism UI components using design tokens
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Alerts */}
|
||||
{showAlert && (
|
||||
<Alert
|
||||
variant="info"
|
||||
title="Component Showcase"
|
||||
dismissible
|
||||
onDismiss={() => setShowAlert(false)}
|
||||
>
|
||||
This showcase demonstrates all the UI primitive components with glassmorphism styling.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Buttons */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-xl font-semibold text-text-primary">Buttons</h2>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="space-y-6">
|
||||
{/* Button Variants */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-text-secondary mb-3">Variants</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="gold">Gold</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="danger">Danger</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Button Sizes */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-text-secondary mb-3">Sizes</h3>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="md">Medium</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Button States */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-text-secondary mb-3">States</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button loading>Loading</Button>
|
||||
<Button disabled>Disabled</Button>
|
||||
<Button
|
||||
iconLeft={
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
With Icon
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* Form Inputs */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-xl font-semibold text-text-primary">Form Inputs</h2>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input
|
||||
label="Email Address"
|
||||
placeholder="Enter your email"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
helperText="We'll never share your email"
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Password"
|
||||
type="password"
|
||||
placeholder="Enter password"
|
||||
iconLeft={
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Error Example"
|
||||
placeholder="This field has an error"
|
||||
error="This field is required"
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="Select an Option"
|
||||
options={selectOptions}
|
||||
value={selectValue}
|
||||
onChange={(value) => setSelectValue(value as string)}
|
||||
placeholder="Choose an option..."
|
||||
helperText="Select from the available options"
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* Badges */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-xl font-semibold text-text-primary">Badges</h2>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-text-secondary mb-3">Variants</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Badge variant="success">Success</Badge>
|
||||
<Badge variant="warning">Warning</Badge>
|
||||
<Badge variant="error">Error</Badge>
|
||||
<Badge variant="info">Info</Badge>
|
||||
<Badge variant="neutral">Neutral</Badge>
|
||||
<Badge variant="gold">Gold</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-text-secondary mb-3">With Dots</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Badge variant="success" dot>Online</Badge>
|
||||
<Badge variant="warning" dot>Pending</Badge>
|
||||
<Badge variant="error" dot>Offline</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-text-secondary mb-3">Removable</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Badge variant="info" removable onRemove={() => { /* removed */ }}>
|
||||
Removable Badge
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<Card variant="default">
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-medium text-text-primary">Default Card</h3>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p className="text-text-secondary">
|
||||
This is a default card with glassmorphism styling.
|
||||
</p>
|
||||
</CardBody>
|
||||
<CardFooter>
|
||||
<Button size="sm">Action</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Card variant="elevated" elevation="lg">
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-medium text-text-primary">Elevated Card</h3>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p className="text-text-secondary">
|
||||
This card has higher elevation with enhanced shadows.
|
||||
</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
clickable
|
||||
onClick={() => { /* card clicked */ }}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<CardBody>
|
||||
<h3 className="text-lg font-medium text-text-primary mb-2">Clickable Card</h3>
|
||||
<p className="text-text-secondary">
|
||||
This card is clickable and will trigger an action.
|
||||
</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Alerts */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-xl font-semibold text-text-primary">Alerts</h2>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div className="space-y-4">
|
||||
<Alert variant="success" title="Success!">
|
||||
Your action was completed successfully.
|
||||
</Alert>
|
||||
|
||||
<Alert variant="warning" title="Warning">
|
||||
Please review your information before proceeding.
|
||||
</Alert>
|
||||
|
||||
<Alert variant="error" title="Error">
|
||||
There was an error processing your request.
|
||||
</Alert>
|
||||
|
||||
<Alert
|
||||
variant="info"
|
||||
title="Information"
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="ghost">Dismiss</Button>
|
||||
<Button size="sm">Learn More</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
Here's some important information you should know.
|
||||
</Alert>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { BarChart3, DollarSign, Users, Calendar } from 'lucide-react';
|
||||
import { BarChart3, DollarSign, Users, Calendar, Plus } from 'lucide-react';
|
||||
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { Card, CardBody } from '../components/ui/Card';
|
||||
|
||||
export function DashboardPage() {
|
||||
const stats = [
|
||||
@@ -7,111 +10,149 @@ export function DashboardPage() {
|
||||
label: 'Total Revenue',
|
||||
value: '$12,450',
|
||||
icon: DollarSign,
|
||||
color: 'text-green-400',
|
||||
change: '+12%',
|
||||
changeType: 'positive' as const,
|
||||
},
|
||||
{
|
||||
label: 'Tickets Sold',
|
||||
value: '1,234',
|
||||
icon: Users,
|
||||
color: 'text-blue-400',
|
||||
change: '+8%',
|
||||
changeType: 'positive' as const,
|
||||
},
|
||||
{
|
||||
label: 'Active Events',
|
||||
value: '8',
|
||||
icon: Calendar,
|
||||
color: 'text-purple-400',
|
||||
change: '+2',
|
||||
changeType: 'positive' as const,
|
||||
},
|
||||
{
|
||||
label: 'Growth Rate',
|
||||
value: '+23%',
|
||||
icon: BarChart3,
|
||||
color: 'text-indigo-400',
|
||||
change: '+5%',
|
||||
changeType: 'positive' as const,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='min-h-screen p-8'>
|
||||
<div className='mx-auto max-w-7xl'>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className='mb-8'
|
||||
>
|
||||
<h1 className='mb-2 text-4xl font-bold text-white'>Dashboard</h1>
|
||||
<p className='text-white/70'>
|
||||
Overview of your events and performance
|
||||
</p>
|
||||
</motion.div>
|
||||
const recentEvents = [
|
||||
{ name: 'Summer Gala', status: 'Active', attendees: 150 },
|
||||
{ name: 'Wedding Reception', status: 'Active', attendees: 80 },
|
||||
{ name: 'Corporate Event', status: 'Draft', attendees: 0 },
|
||||
];
|
||||
|
||||
<div className='mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4'>
|
||||
{stats.map((stat, index) => (
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Stats grid */}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{stats.map((stat, index) => {
|
||||
const Icon = stat.icon;
|
||||
return (
|
||||
<motion.div
|
||||
key={stat.label}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
className='glass rounded-2xl p-6'
|
||||
>
|
||||
<div className='mb-2 flex items-center justify-between'>
|
||||
<stat.icon className={`h-8 w-8 ${stat.color}`} />
|
||||
<span className={`text-2xl font-bold ${stat.color}`}>
|
||||
{stat.value}
|
||||
<Card className="h-full">
|
||||
<CardBody className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="p-2 bg-gold-100 dark:bg-gold-900/20 rounded-lg">
|
||||
<Icon className="h-6 w-6 text-gold-600 dark:text-gold-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-green-600 dark:text-green-400">
|
||||
{stat.change}
|
||||
</span>
|
||||
</div>
|
||||
<p className='text-sm text-white/70'>{stat.label}</p>
|
||||
<div className="space-y-1">
|
||||
<p className="text-2xl font-bold text-slate-900 dark:text-slate-100">
|
||||
{stat.value}
|
||||
</p>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-400">
|
||||
{stat.label}
|
||||
</p>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 gap-6 lg:grid-cols-2'>
|
||||
{/* Main content grid */}
|
||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||
{/* Recent Events */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className='glass rounded-2xl p-6'
|
||||
>
|
||||
<h2 className='mb-4 text-2xl font-semibold text-white'>
|
||||
<Card>
|
||||
<CardBody className="p-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-6">
|
||||
Recent Events
|
||||
</h2>
|
||||
<div className='space-y-4'>
|
||||
{['Summer Gala', 'Wedding Reception', 'Corporate Event'].map(
|
||||
event => (
|
||||
<div className="space-y-4">
|
||||
{recentEvents.map((event) => (
|
||||
<div
|
||||
key={event}
|
||||
className='flex items-center justify-between rounded-lg bg-white/5 p-4'
|
||||
key={event.name}
|
||||
className="flex items-center justify-between p-4 rounded-lg
|
||||
bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700"
|
||||
>
|
||||
<span className='text-white'>{event}</span>
|
||||
<span className='text-white/70'>Active</span>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-slate-900 dark:text-slate-100">
|
||||
{event.name}
|
||||
</p>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-400">
|
||||
{event.attendees} attendees
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
event.status === 'Active'
|
||||
? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400'
|
||||
: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400'
|
||||
}`}>
|
||||
{event.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 }}
|
||||
className='glass rounded-2xl p-6'
|
||||
>
|
||||
<h2 className='mb-4 text-2xl font-semibold text-white'>
|
||||
<Card>
|
||||
<CardBody className="p-6">
|
||||
<h2 className="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-6">
|
||||
Quick Actions
|
||||
</h2>
|
||||
<div className='space-y-3'>
|
||||
<button className='w-full rounded-lg bg-gradient-to-r from-blue-500 to-purple-600 p-3 font-semibold text-white transition-all duration-200 hover:from-blue-600 hover:to-purple-700'>
|
||||
<div className="space-y-4">
|
||||
<Button variant="primary" size="lg" className="w-full justify-center">
|
||||
<Plus className="h-5 w-5 mr-2" />
|
||||
Create New Event
|
||||
</button>
|
||||
<button className='glass w-full rounded-lg p-3 font-semibold text-white transition-all duration-200 hover:bg-white/20'>
|
||||
</Button>
|
||||
<Button variant="secondary" size="lg" className="w-full justify-center">
|
||||
<BarChart3 className="h-5 w-5 mr-2" />
|
||||
View Analytics
|
||||
</button>
|
||||
<button className='glass w-full rounded-lg p-3 font-semibold text-white transition-all duration-200 hover:bg-white/20'>
|
||||
</Button>
|
||||
<Button variant="ghost" size="lg" className="w-full justify-center">
|
||||
<Users className="h-5 w-5 mr-2" />
|
||||
Manage Attendees
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Calendar, MapPin, Users, Clock } from 'lucide-react';
|
||||
import { Calendar, MapPin, Users, Clock, Plus, Edit, Eye } from 'lucide-react';
|
||||
|
||||
import { Badge } from '../components/ui/Badge';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { Card, CardBody } from '../components/ui/Card';
|
||||
|
||||
export function EventsPage() {
|
||||
const events = [
|
||||
@@ -10,7 +14,7 @@ export function EventsPage() {
|
||||
time: '7:00 PM',
|
||||
location: 'Grand Ballroom',
|
||||
attendees: 150,
|
||||
status: 'active',
|
||||
status: 'active' as const,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@@ -19,7 +23,7 @@ export function EventsPage() {
|
||||
time: '5:30 PM',
|
||||
location: 'Garden Pavilion',
|
||||
attendees: 200,
|
||||
status: 'active',
|
||||
status: 'active' as const,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
@@ -28,85 +32,94 @@ export function EventsPage() {
|
||||
time: '6:00 PM',
|
||||
location: 'Conference Center',
|
||||
attendees: 80,
|
||||
status: 'draft',
|
||||
status: 'draft' as const,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='min-h-screen p-8'>
|
||||
<div className='mx-auto max-w-7xl'>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className='mb-8 flex items-center justify-between'
|
||||
>
|
||||
<div>
|
||||
<h1 className='mb-2 text-4xl font-bold text-white'>Events</h1>
|
||||
<p className='text-white/70'>
|
||||
Manage your events and track performance
|
||||
</p>
|
||||
</div>
|
||||
<button className='transform rounded-lg bg-gradient-to-r from-blue-500 to-purple-600 px-6 py-3 font-semibold text-white transition-all duration-200 hover:scale-105 hover:from-blue-600 hover:to-purple-700'>
|
||||
<div className="space-y-8">
|
||||
{/* Action bar */}
|
||||
<div className="flex justify-end">
|
||||
<Button variant="primary" size="lg">
|
||||
<Plus className="h-5 w-5 mr-2" />
|
||||
Create Event
|
||||
</button>
|
||||
</motion.div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3'>
|
||||
{/* Events grid */}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{events.map((event, index) => (
|
||||
<motion.div
|
||||
key={event.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
className='glass cursor-pointer rounded-2xl p-6 transition-all duration-200 hover:bg-white/15'
|
||||
>
|
||||
<div className='mb-4 flex items-start justify-between'>
|
||||
<h3 className='text-xl font-semibold text-white'>
|
||||
<Card className="h-full cursor-pointer hover:shadow-lg transition-shadow duration-200">
|
||||
<CardBody className="p-6">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<h3 className="text-xl font-semibold text-slate-900 dark:text-slate-100 line-clamp-2">
|
||||
{event.title}
|
||||
</h3>
|
||||
<span
|
||||
className={`rounded-full px-3 py-1 text-xs font-medium ${
|
||||
event.status === 'active'
|
||||
? 'bg-green-500/20 text-green-400'
|
||||
: 'bg-yellow-500/20 text-yellow-400'
|
||||
}`}
|
||||
<Badge
|
||||
variant={event.status === 'active' ? 'success' : 'warning'}
|
||||
className="ml-2 flex-shrink-0"
|
||||
>
|
||||
{event.status}
|
||||
</span>
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className='mb-6 space-y-3'>
|
||||
<div className='flex items-center text-white/70'>
|
||||
<Calendar className='mr-2 h-4 w-4' />
|
||||
<span>{event.date}</span>
|
||||
<div className="space-y-3 mb-6">
|
||||
<div className="flex items-center text-slate-600 dark:text-slate-400">
|
||||
<Calendar className="mr-3 h-4 w-4 flex-shrink-0" />
|
||||
<span className="text-sm">{event.date}</span>
|
||||
</div>
|
||||
<div className='flex items-center text-white/70'>
|
||||
<Clock className='mr-2 h-4 w-4' />
|
||||
<span>{event.time}</span>
|
||||
<div className="flex items-center text-slate-600 dark:text-slate-400">
|
||||
<Clock className="mr-3 h-4 w-4 flex-shrink-0" />
|
||||
<span className="text-sm">{event.time}</span>
|
||||
</div>
|
||||
<div className='flex items-center text-white/70'>
|
||||
<MapPin className='mr-2 h-4 w-4' />
|
||||
<span>{event.location}</span>
|
||||
<div className="flex items-center text-slate-600 dark:text-slate-400">
|
||||
<MapPin className="mr-3 h-4 w-4 flex-shrink-0" />
|
||||
<span className="text-sm">{event.location}</span>
|
||||
</div>
|
||||
<div className='flex items-center text-white/70'>
|
||||
<Users className='mr-2 h-4 w-4' />
|
||||
<span>{event.attendees} attendees</span>
|
||||
<div className="flex items-center text-slate-600 dark:text-slate-400">
|
||||
<Users className="mr-3 h-4 w-4 flex-shrink-0" />
|
||||
<span className="text-sm">{event.attendees} attendees</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex space-x-2'>
|
||||
<button className='flex-1 rounded-lg bg-white/10 px-4 py-2 font-medium text-white transition-all duration-200 hover:bg-white/20'>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="ghost" size="sm" className="flex-1">
|
||||
<Edit className="h-4 w-4 mr-1" />
|
||||
Edit
|
||||
</button>
|
||||
<button className='flex-1 rounded-lg bg-blue-500/20 px-4 py-2 font-medium text-blue-400 transition-all duration-200 hover:bg-blue-500/30'>
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm" className="flex-1">
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
View
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Empty state when no events */}
|
||||
{events.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<Calendar className="h-12 w-12 mx-auto text-slate-400 mb-4" />
|
||||
<h3 className="text-lg font-medium text-slate-900 dark:text-slate-100 mb-2">
|
||||
No events yet
|
||||
</h3>
|
||||
<p className="text-slate-600 dark:text-slate-400 mb-6">
|
||||
Get started by creating your first event
|
||||
</p>
|
||||
<Button variant="primary">
|
||||
<Plus className="h-5 w-5 mr-2" />
|
||||
Create Your First Event
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,84 +1,180 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { Ticket, Calendar, Users } from 'lucide-react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { ArrowRight, Calendar, Users, BarChart3, Zap } from 'lucide-react';
|
||||
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { Card, CardHeader, CardBody } from '../components/ui/Card';
|
||||
|
||||
/**
|
||||
* HomePage component - Public landing page
|
||||
*
|
||||
* Features:
|
||||
* - Hero section with branding
|
||||
* - Feature highlights
|
||||
* - Call-to-action buttons
|
||||
* - Responsive design with glassmorphism theme
|
||||
*/
|
||||
export function HomePage() {
|
||||
const features = [
|
||||
{
|
||||
icon: Calendar,
|
||||
title: 'Event Management',
|
||||
description: 'Create and manage events with our intuitive dashboard.',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'Customer Insights',
|
||||
description: 'Track attendees and gain valuable insights about your audience.',
|
||||
},
|
||||
{
|
||||
icon: BarChart3,
|
||||
title: 'Analytics',
|
||||
description: 'Monitor ticket sales and revenue with detailed analytics.',
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
title: 'Fast & Reliable',
|
||||
description: 'Quick setup and reliable performance for all event sizes.',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='flex min-h-screen items-center justify-center p-8'>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className='mx-auto max-w-4xl text-center'
|
||||
>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className='mb-6 bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-6xl font-bold text-transparent text-white'
|
||||
>
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
|
||||
{/* Navigation */}
|
||||
<nav className="border-b border-slate-200 dark:border-slate-700 bg-white/80 dark:bg-slate-900/80 backdrop-blur-md">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between items-center h-16">
|
||||
<div className="flex items-center">
|
||||
<div className="h-8 w-8 bg-gradient-to-br from-gold-400 to-gold-600 rounded-lg
|
||||
flex items-center justify-center mr-3">
|
||||
<Calendar className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<span className="text-xl font-bold text-slate-900 dark:text-slate-100">
|
||||
Black Canyon Tickets
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className='mb-12 text-xl text-white/80'
|
||||
>
|
||||
Premium event ticketing platform with beautiful glassmorphism design
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 }}
|
||||
className='mb-12 grid grid-cols-1 gap-8 md:grid-cols-3'
|
||||
>
|
||||
<div className='glass rounded-2xl p-6 text-center'>
|
||||
<Ticket className='mx-auto mb-4 h-12 w-12 text-blue-400' />
|
||||
<h3 className='mb-2 text-xl font-semibold text-white'>
|
||||
Premium Tickets
|
||||
</h3>
|
||||
<p className='text-white/70'>
|
||||
Beautiful ticket designs for upscale events
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='glass rounded-2xl p-6 text-center'>
|
||||
<Calendar className='mx-auto mb-4 h-12 w-12 text-purple-400' />
|
||||
<h3 className='mb-2 text-xl font-semibold text-white'>
|
||||
Event Management
|
||||
</h3>
|
||||
<p className='text-white/70'>
|
||||
Comprehensive tools for event organizers
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='glass rounded-2xl p-6 text-center'>
|
||||
<Users className='mx-auto mb-4 h-12 w-12 text-indigo-400' />
|
||||
<h3 className='mb-2 text-xl font-semibold text-white'>
|
||||
Guest Experience
|
||||
</h3>
|
||||
<p className='text-white/70'>
|
||||
Seamless check-in and attendee management
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.8 }}
|
||||
className='space-x-4'
|
||||
>
|
||||
<button className='transform rounded-lg bg-gradient-to-r from-blue-500 to-purple-600 px-8 py-3 font-semibold text-white transition-all duration-200 hover:scale-105 hover:from-blue-600 hover:to-purple-700'>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link to="/login">
|
||||
<Button variant="ghost">
|
||||
Sign In
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/login">
|
||||
<Button>
|
||||
Get Started
|
||||
</button>
|
||||
<button className='glass rounded-lg px-8 py-3 font-semibold text-white transition-all duration-200 hover:bg-white/20'>
|
||||
Learn More
|
||||
</button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="py-20 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<h1 className="text-5xl sm:text-6xl font-bold text-slate-900 dark:text-slate-100 mb-6">
|
||||
Premium Ticketing for{' '}
|
||||
<span className="bg-gradient-to-r from-gold-400 to-gold-600 bg-clip-text text-transparent">
|
||||
Upscale Events
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-slate-600 dark:text-slate-400 mb-8 max-w-2xl mx-auto">
|
||||
Black Canyon Tickets is the self-service ticketing platform designed specifically for
|
||||
high-end venues. Manage dance performances, weddings, galas, and more with elegance.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link to="/login">
|
||||
<Button size="lg" className="w-full sm:w-auto">
|
||||
Start Your Free Trial
|
||||
<ArrowRight className="ml-2 h-5 w-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/showcase">
|
||||
<Button variant="ghost" size="lg" className="w-full sm:w-auto">
|
||||
View Demo
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
<section className="py-20 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-4">
|
||||
Everything you need to succeed
|
||||
</h2>
|
||||
<p className="text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto">
|
||||
Our platform provides all the tools you need to create, manage, and analyze
|
||||
your premium events.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{features.map((feature, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
className="bg-white/80 dark:bg-slate-800/80 backdrop-blur-md border-slate-200/60 dark:border-slate-700/60
|
||||
hover:bg-white/90 dark:hover:bg-slate-800/90 transition-all duration-200"
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="h-12 w-12 bg-gradient-to-br from-gold-400 to-gold-600 rounded-lg
|
||||
flex items-center justify-center mb-4">
|
||||
<feature.icon className="h-6 w-6 text-white" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100">
|
||||
{feature.title}
|
||||
</h3>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<p className="text-slate-600 dark:text-slate-400">
|
||||
{feature.description}
|
||||
</p>
|
||||
</CardBody>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<Card className="bg-gradient-to-r from-gold-400 to-gold-600 border-0 text-white">
|
||||
<CardBody className="p-12 text-center">
|
||||
<h2 className="text-3xl font-bold mb-4">
|
||||
Ready to elevate your events?
|
||||
</h2>
|
||||
<p className="text-xl mb-8 text-gold-100">
|
||||
Join the premium venues that trust Black Canyon Tickets for their most important events.
|
||||
</p>
|
||||
<Link to="/login">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-white text-gold-600 hover:bg-slate-50 border-white hover:border-slate-100"
|
||||
>
|
||||
Get Started Today
|
||||
<ArrowRight className="ml-2 h-5 w-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-slate-200 dark:border-slate-700 bg-white/80 dark:bg-slate-900/80 backdrop-blur-md">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="text-center text-slate-600 dark:text-slate-400">
|
||||
<p>© 2024 Black Canyon Tickets. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,11 +14,32 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"allowUnusedLabels": false,
|
||||
"allowUnreachableCode": false,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
|
||||
/* Module Resolution Options */
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
/* Path mapping */
|
||||
"baseUrl": ".",
|
||||
|
||||
Reference in New Issue
Block a user